<commit_before>package ircmessage

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"strings"
)

const (
	maxMessageSize = 512
	runeAt         = '@'
	runeColon      = ':'
	runeSemicolon  = ';'
	runeSpace      = ' '
	runeEquals     = '='
	tokenEquals    = "="
	tokenColon     = ":"
	tokenSemicolon = ";"
	tokenSpace     = " "
)

var (
	ErrUnexpectedEOF    = errors.New("unexpected EOF")
	ErrMessageMalformed = errors.New("message malformed")
)

type Scanner struct {
	src            *bufio.Reader
	buf            *bytes.Buffer // Temporary buffer that are re-used where possible.
	rawBuf         []rune        // Keeps track of the current raw IRC message
	currentMsgSize int
	lastRuneSize   int // There is never a need to unread further than one rune, so this is enough.
}

func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		src: bufio.NewReader(r),
		buf: &bytes.Buffer{},
	}
}

func (s *Scanner) read() (rune, error) {
	rn, n, err := s.src.ReadRune()
	if err != nil {
		return 0, err
	}
	s.lastRuneSize = n
	s.currentMsgSize += n
	s.rawBuf = append(s.rawBuf, rn)
	if s.currentMsgSize > maxMessageSize {
		return 0, ErrMessageMalformed
	}
	return rn, err
}

func (s *Scanner) unread() error {
	if err := s.src.UnreadRune(); err != nil {
		return err
	}
	s.currentMsgSize -= s.lastRuneSize
	s.rawBuf = s.rawBuf[:len(s.rawBuf)-1]
	return nil
}

type Message struct {
	Raw     string
	Tags    map[string]string
	Prefix  string
	Command string
	Params  []string
}

func (s *Scanner) skipSpace() {
	for {
		ch, _ := s.read()
		if ch != runeSpace {
			s.unread()
			break
		}
	}
}

func (s *Scanner) readTags() (map[string]string, error) {
	// Read whole tag string.
	s.buf.Reset()
	for {
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return nil, ErrUnexpectedEOF
			}
			return nil, err
		}
		if ch == runeSpace {
			break
		}
		if s.buf.Len() >= maxMessageSize {
			return nil, ErrMessageMalformed
		}
		s.buf.WriteRune(ch)
	}
	// Split tags.
	tagMap := make(map[string]string)
	var tags []string
	if !strings.Contains(s.buf.String(), tokenSemicolon) {
		tags = append(tags, s.buf.String())
	} else {
		splitTags := strings.Split(s.buf.String(), tokenSemicolon)
		for _, v := range splitTags {
			if strings.Contains(v, tokenEquals) {
				pair := strings.Split(v, tokenEquals)
				if len(pair) < 2 || len(pair) > 2 {
					return nil, ErrMessageMalformed
				}
				tagMap[pair[0]] = pair[1]
				continue
			}
			tagMap[v] = ""
		}
	}
	s.skipSpace()
	return tagMap, nil
}

func (s *Scanner) readPrefix() (string, error) {
	s.buf.Reset()
	for {
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return "", ErrUnexpectedEOF
			}
			return "", err
		}
		if ch == runeSpace {
			break
		}
		s.buf.WriteRune(ch)
	}
	prefix := s.buf.String()
	s.skipSpace()
	return prefix, nil
}

func (s *Scanner) readCommand() (string, error) {
	s.buf.Reset()
	for {
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return "", ErrUnexpectedEOF
			}
			return "", err
		}
		if ch == runeSpace {
			break
		}
		if ch == '\r' {
			s.unread()
			break
		}
		s.buf.WriteRune(ch)
	}
	s.skipSpace()
	return s.buf.String(), nil
}

func (s *Scanner) readTrailingParam() (string, error) {
	s.buf.Reset()
	for {
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return "", ErrUnexpectedEOF
			}
			return "", err
		}
		if ch == '\r' {
			if ch, _ := s.read(); ch != '\n' {
				return "", ErrMessageMalformed
			}
			break
		}
		s.buf.WriteRune(ch)
	}
	return s.buf.String(), nil
}

func (s *Scanner) readParams() ([]string, error) {
	var params []string
	s.buf.Reset()
	for {
		if end, _ := s.isLineEnd(); end {
			break
		}
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return nil, ErrUnexpectedEOF
			}
			return nil, err
		}
		s.buf.WriteRune(ch)
	}
	// A colon indicates a trailing parameter, read
	// everything from after the colon to line ending
	// and return it as a string slice with a single
	// element.
	paramString := strings.Split(s.buf.String(), tokenSpace)
	for i, v := range paramString {
		if strings.HasPrefix(v, tokenColon) {
			paramString[i] = paramString[i][1:]
			params = append(params, strings.Join(paramString[i:], tokenSpace))
			break
		}
		if v != "" {
			params = append(params, v)
		}
	}
	return params, nil
}

func (s *Scanner) isLineEnd() (bool, error) {
	ch, err := s.read()
	if err != nil {
		return false, err
	}
	if ch == '\r' {
		ch, err := s.read()
		if err != nil {
			return false, err
		}
		if ch == '\n' {
			return true, nil
		}
		s.unread()
	}
	s.unread()
	return false, nil
}

func (s *Scanner) Next() (Message, error) {
	s.rawBuf = make([]rune, 0, 1024)
	s.currentMsgSize = 0
	var msg Message
	ch, err := s.read()
	if err != nil {
		return Message{}, err
	}
	// Check for and read message tags if present as per:
	// http://ircv3.net/specs/core/message-tags-3.2.html
	if ch == runeAt {
		msg.Tags, err = s.readTags()
		if err != nil {
			return Message{}, err
		}
		// Reset the size counter. Tags can be a maximum of 512 bytes
		// and the remainder of the message is allowed a further 512.
		s.currentMsgSize = 0
		// Get next rune
		ch, err = s.read()
		if err != nil {
			if err == io.EOF {
				return Message{}, ErrUnexpectedEOF
			}
			return Message{}, err
		}
	}
	// Read message prefix if present, prefixes are
	// prepended with a colon.
	if ch == runeColon {
		msg.Prefix, err = s.readPrefix()
		if err != nil {
			return Message{}, err
		}
	} else {
		s.unread()
	}
	msg.Command, err = s.readCommand()
	if err != nil {
		return Message{}, err
	}
	// Check for line ending, else start reading params.
	end, err := s.isLineEnd()
	if err != nil {
		return Message{}, err
	}
	if end {
		msg.Raw = string(s.rawBuf)
		return msg, nil
	}
	s.unread()
	msg.Params, err = s.readParams()
	if err != nil {
		return Message{}, err
	}
	msg.Raw = string(s.rawBuf)
	return msg, nil
}
<commit_msg>Fix comment about trailing params<commit_after>package ircmessage

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"strings"
)

const (
	maxMessageSize = 512
	runeAt         = '@'
	runeColon      = ':'
	runeSemicolon  = ';'
	runeSpace      = ' '
	runeEquals     = '='
	tokenEquals    = "="
	tokenColon     = ":"
	tokenSemicolon = ";"
	tokenSpace     = " "
)

var (
	ErrUnexpectedEOF    = errors.New("unexpected EOF")
	ErrMessageMalformed = errors.New("message malformed")
)

type Scanner struct {
	src            *bufio.Reader
	buf            *bytes.Buffer // Temporary buffer that are re-used where possible.
	rawBuf         []rune        // Keeps track of the current raw IRC message
	currentMsgSize int
	lastRuneSize   int // There is never a need to unread further than one rune, so this is enough.
}

func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		src: bufio.NewReader(r),
		buf: &bytes.Buffer{},
	}
}

func (s *Scanner) read() (rune, error) {
	rn, n, err := s.src.ReadRune()
	if err != nil {
		return 0, err
	}
	s.lastRuneSize = n
	s.currentMsgSize += n
	s.rawBuf = append(s.rawBuf, rn)
	if s.currentMsgSize > maxMessageSize {
		return 0, ErrMessageMalformed
	}
	return rn, err
}

func (s *Scanner) unread() error {
	if err := s.src.UnreadRune(); err != nil {
		return err
	}
	s.currentMsgSize -= s.lastRuneSize
	s.rawBuf = s.rawBuf[:len(s.rawBuf)-1]
	return nil
}

type Message struct {
	Raw     string
	Tags    map[string]string
	Prefix  string
	Command string
	Params  []string
}

func (s *Scanner) skipSpace() {
	for {
		ch, _ := s.read()
		if ch != runeSpace {
			s.unread()
			break
		}
	}
}

func (s *Scanner) readTags() (map[string]string, error) {
	// Read whole tag string.
	s.buf.Reset()
	for {
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return nil, ErrUnexpectedEOF
			}
			return nil, err
		}
		if ch == runeSpace {
			break
		}
		if s.buf.Len() >= maxMessageSize {
			return nil, ErrMessageMalformed
		}
		s.buf.WriteRune(ch)
	}
	// Split tags.
	tagMap := make(map[string]string)
	var tags []string
	if !strings.Contains(s.buf.String(), tokenSemicolon) {
		tags = append(tags, s.buf.String())
	} else {
		splitTags := strings.Split(s.buf.String(), tokenSemicolon)
		for _, v := range splitTags {
			if strings.Contains(v, tokenEquals) {
				pair := strings.Split(v, tokenEquals)
				if len(pair) < 2 || len(pair) > 2 {
					return nil, ErrMessageMalformed
				}
				tagMap[pair[0]] = pair[1]
				continue
			}
			tagMap[v] = ""
		}
	}
	s.skipSpace()
	return tagMap, nil
}

func (s *Scanner) readPrefix() (string, error) {
	s.buf.Reset()
	for {
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return "", ErrUnexpectedEOF
			}
			return "", err
		}
		if ch == runeSpace {
			break
		}
		s.buf.WriteRune(ch)
	}
	prefix := s.buf.String()
	s.skipSpace()
	return prefix, nil
}

func (s *Scanner) readCommand() (string, error) {
	s.buf.Reset()
	for {
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return "", ErrUnexpectedEOF
			}
			return "", err
		}
		if ch == runeSpace {
			break
		}
		if ch == '\r' {
			s.unread()
			break
		}
		s.buf.WriteRune(ch)
	}
	s.skipSpace()
	return s.buf.String(), nil
}

func (s *Scanner) readTrailingParam() (string, error) {
	s.buf.Reset()
	for {
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return "", ErrUnexpectedEOF
			}
			return "", err
		}
		if ch == '\r' {
			if ch, _ := s.read(); ch != '\n' {
				return "", ErrMessageMalformed
			}
			break
		}
		s.buf.WriteRune(ch)
	}
	return s.buf.String(), nil
}

func (s *Scanner) readParams() ([]string, error) {
	var params []string
	s.buf.Reset()
	for {
		if end, _ := s.isLineEnd(); end {
			break
		}
		ch, err := s.read()
		if err != nil {
			if err == io.EOF {
				return nil, ErrUnexpectedEOF
			}
			return nil, err
		}
		s.buf.WriteRune(ch)
	}
	// A colon indicates a trailing parameter, read
	// everything from after the colon to line ending
	// and append it to params.
	paramString := strings.Split(s.buf.String(), tokenSpace)
	for i, v := range paramString {
		if strings.HasPrefix(v, tokenColon) {
			paramString[i] = paramString[i][1:]
			params = append(params, strings.Join(paramString[i:], tokenSpace))
			break
		}
		if v != "" {
			params = append(params, v)
		}
	}
	return params, nil
}

func (s *Scanner) isLineEnd() (bool, error) {
	ch, err := s.read()
	if err != nil {
		return false, err
	}
	if ch == '\r' {
		ch, err := s.read()
		if err != nil {
			return false, err
		}
		if ch == '\n' {
			return true, nil
		}
		s.unread()
	}
	s.unread()
	return false, nil
}

func (s *Scanner) Next() (Message, error) {
	s.rawBuf = make([]rune, 0, 1024)
	s.currentMsgSize = 0
	var msg Message
	ch, err := s.read()
	if err != nil {
		return Message{}, err
	}
	// Check for and read message tags if present as per:
	// http://ircv3.net/specs/core/message-tags-3.2.html
	if ch == runeAt {
		msg.Tags, err = s.readTags()
		if err != nil {
			return Message{}, err
		}
		// Reset the size counter. Tags can be a maximum of 512 bytes
		// and the remainder of the message is allowed a further 512.
		s.currentMsgSize = 0
		// Get next rune
		ch, err = s.read()
		if err != nil {
			if err == io.EOF {
				return Message{}, ErrUnexpectedEOF
			}
			return Message{}, err
		}
	}
	// Read message prefix if present, prefixes are
	// prepended with a colon.
	if ch == runeColon {
		msg.Prefix, err = s.readPrefix()
		if err != nil {
			return Message{}, err
		}
	} else {
		s.unread()
	}
	msg.Command, err = s.readCommand()
	if err != nil {
		return Message{}, err
	}
	// Check for line ending, else start reading params.
	end, err := s.isLineEnd()
	if err != nil {
		return Message{}, err
	}
	if end {
		msg.Raw = string(s.rawBuf)
		return msg, nil
	}
	s.unread()
	msg.Params, err = s.readParams()
	if err != nil {
		return Message{}, err
	}
	msg.Raw = string(s.rawBuf)
	return msg, nil
}
<|endoftext|>


<commit_before>package config

import (
	"github.com/ghodss/yaml"
	os_servers "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	log "tes/logger"
	pbr "tes/server/proto"
	"time"
)

// Weights describes the scheduler score weights.
// All fields should be float32 type.
type Weights map[string]float32

// StorageConfig describes configuration for all storage types
type StorageConfig struct {
	Local LocalStorage
	S3    S3Storage
	GS    GSStorage
}

// LocalStorage describes the directories TES can read from and write to
type LocalStorage struct {
	AllowedDirs []string
}

// GSStorage describes configuration for the Google Cloud storage backend.
type GSStorage struct {
	AccountFile string
	FromEnv     bool
}

// Valid validates the GSStorage configuration.
func (g GSStorage) Valid() bool {
	return g.FromEnv || g.AccountFile != ""
}

// Valid validates the LocalStorage configuration
func (l LocalStorage) Valid() bool {
	return len(l.AllowedDirs) > 0
}

// S3Storage describes the directories TES can read from and write to
type S3Storage struct {
	Endpoint string
	Key      string
	Secret   string
}

// Valid validates the LocalStorage configuration
func (l S3Storage) Valid() bool {
	return l.Endpoint != "" && l.Key != "" && l.Secret != ""
}

// LocalScheduler describes configuration for the local scheduler.
type LocalScheduler struct {
	Weights Weights
}

// OpenStackScheduler describes configuration for the openstack scheduler.
type OpenStackScheduler struct {
	KeyPair    string
	ConfigPath string
	Server     os_servers.CreateOpts
	Weights    Weights
}

// GCEScheduler describes configuration for the Google Cloud scheduler.
type GCEScheduler struct {
	AccountFile string
	Project     string
	Zone        string
	Templates   []string
	Weights     Weights
}

// Schedulers describes configuration for all schedulers.
type Schedulers struct {
	Local     LocalScheduler
	Dumblocal LocalScheduler
	Condor    LocalScheduler
	OpenStack OpenStackScheduler
	GCE       GCEScheduler
}

// Config describes configuration for TES.
type Config struct {
	Storage       []*StorageConfig
	ServerAddress string
	Scheduler     string
	Schedulers    Schedulers
	Worker        Worker
	DBPath        string
	HTTPPort      string
	RPCPort       string
	ContentDir    string
	WorkDir       string
	LogLevel      string
	MaxJobLogSize int
	ScheduleRate  time.Duration
	ScheduleChunk int
	// How long to wait for a worker ping before marking it as dead
	WorkerPingTimeout time.Duration
	// How long to wait for worker initialization before marking it dead
	WorkerInitTimeout time.Duration
}

// DefaultConfig returns configuration with simple defaults.
func DefaultConfig() Config {
	workDir := "tes-work-dir"
	return Config{
		ServerAddress: "localhost:9090",
		DBPath:        path.Join(workDir, "tes_task.db"),
		HTTPPort:      "8000",
		RPCPort:       "9090",
		ContentDir:    defaultContentDir(),
		WorkDir:       workDir,
		LogLevel:      "debug",
		Scheduler:     "local",
		Schedulers: Schedulers{
			Local: LocalScheduler{},
			GCE: GCEScheduler{
				Weights: Weights{
					"startup time": 1.0,
				},
			},
		},
		Worker:            WorkerDefaultConfig(),
		MaxJobLogSize:     10000,
		ScheduleRate:      time.Second,
		ScheduleChunk:     10,
		WorkerPingTimeout: time.Minute,
		WorkerInitTimeout: time.Minute * 5,
	}
}

// Worker contains worker configuration.
type Worker struct {
	ID string
	// Address of the scheduler, e.g. "1.2.3.4:9090"
	ServerAddress string
	// Directory to write job files to
	WorkDir string
	// How long (seconds) to wait before tearing down an inactive worker
	// Default, -1, indicates to tear down the worker immediately after completing
	// its job
	Timeout time.Duration
	// How often the worker sends update requests to the server
	UpdateRate time.Duration
	// How often the worker sends job log updates
	LogUpdateRate time.Duration
	TrackerRate   time.Duration
	LogTailSize   int64
	Storage       []*StorageConfig
	LogPath       string
	LogLevel      string
	Resources     *pbr.Resources
	// Timeout duration for UpdateWorker() and UpdateJobLogs() RPC calls
	UpdateTimeout time.Duration
	Metadata      map[string]string
}

// WorkerDefaultConfig returns simple, default worker configuration.
func WorkerDefaultConfig() Worker {
	return Worker{
		ServerAddress: "localhost:9090",
		WorkDir:       "tes-work-dir",
		Timeout:       -1,
		// TODO these get reset to zero when not found in yaml?
		UpdateRate:    time.Second * 5,
		LogUpdateRate: time.Second * 5,
		TrackerRate:   time.Second * 5,
		LogTailSize:   10000,
		LogLevel:      "debug",
		UpdateTimeout: time.Second,
	}
}

// ToYaml formats the configuration into YAML and returns the bytes.
func (c Worker) ToYaml() []byte {
	// TODO handle error
	yamlstr, _ := yaml.Marshal(c)
	return yamlstr
}

// ToYamlFile writes the configuration to a YAML file.
func (c Worker) ToYamlFile(p string) {
	// TODO handle error
	ioutil.WriteFile(p, c.ToYaml(), 0600)
}

// ToYamlTempFile writes the configuration to a YAML temp. file.
func (c Worker) ToYamlTempFile(name string) (string, func()) {
	// I'm creating a temp. directory instead of a temp. file so that
	// the file can have an expected name. This is helpful for the HTCondor scheduler.
	tmpdir, _ := ioutil.TempDir("", "")

	cleanup := func() {
		os.RemoveAll(tmpdir)
	}

	p := filepath.Join(tmpdir, name)
	c.ToYamlFile(p)
	return p, cleanup
}

func defaultContentDir() string {
	// TODO this depends on having the entire repo available
	//      which prevents us from releasing a single binary.
	//      Not the worst, but maybe there's a good way to make it optional.
	// TODO handle error
	dir, _ := filepath.Abs(os.Args[0])
	return filepath.Join(dir, "..", "..", "share")
}

// ParseConfigFile parses a TES config file, which is formatted in YAML,
// and returns a Config struct.
func ParseConfigFile(path string, doc interface{}) error {
	source, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	err = yaml.Unmarshal(source, &doc)
	if err != nil {
		return err
	}
	return nil
}

// LoadConfigOrExit tries to load the config from the given file.
// If the file cannot be loaded, os.Exit() is called.
func LoadConfigOrExit(relpath string, config interface{}) {
	var err error
	if relpath != "" {
		var abspath string
		abspath, err = filepath.Abs(relpath)
		if err != nil {
			log.Error("Failure reading config", "path", abspath, "error", err)
			os.Exit(1)
		}
		log.Info("Using config file", "path", abspath)
		err = ParseConfigFile(abspath, &config)
		if err != nil {
			log.Error("Failure reading config", "path", abspath, "error", err)
			os.Exit(1)
		}
	}
}
<commit_msg>Remove unneeded dumblocal config<commit_after>package config

import (
	"github.com/ghodss/yaml"
	os_servers "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	log "tes/logger"
	pbr "tes/server/proto"
	"time"
)

// Weights describes the scheduler score weights.
// All fields should be float32 type.
type Weights map[string]float32

// StorageConfig describes configuration for all storage types
type StorageConfig struct {
	Local LocalStorage
	S3    S3Storage
	GS    GSStorage
}

// LocalStorage describes the directories TES can read from and write to
type LocalStorage struct {
	AllowedDirs []string
}

// GSStorage describes configuration for the Google Cloud storage backend.
type GSStorage struct {
	AccountFile string
	FromEnv     bool
}

// Valid validates the GSStorage configuration.
func (g GSStorage) Valid() bool {
	return g.FromEnv || g.AccountFile != ""
}

// Valid validates the LocalStorage configuration
func (l LocalStorage) Valid() bool {
	return len(l.AllowedDirs) > 0
}

// S3Storage describes the directories TES can read from and write to
type S3Storage struct {
	Endpoint string
	Key      string
	Secret   string
}

// Valid validates the LocalStorage configuration
func (l S3Storage) Valid() bool {
	return l.Endpoint != "" && l.Key != "" && l.Secret != ""
}

// LocalScheduler describes configuration for the local scheduler.
type LocalScheduler struct {
	Weights Weights
}

// OpenStackScheduler describes configuration for the openstack scheduler.
type OpenStackScheduler struct {
	KeyPair    string
	ConfigPath string
	Server     os_servers.CreateOpts
	Weights    Weights
}

// GCEScheduler describes configuration for the Google Cloud scheduler.
type GCEScheduler struct {
	AccountFile string
	Project     string
	Zone        string
	Templates   []string
	Weights     Weights
}

// Schedulers describes configuration for all schedulers.
type Schedulers struct {
	Local     LocalScheduler
	Condor    LocalScheduler
	OpenStack OpenStackScheduler
	GCE       GCEScheduler
}

// Config describes configuration for TES.
type Config struct {
	Storage       []*StorageConfig
	ServerAddress string
	Scheduler     string
	Schedulers    Schedulers
	Worker        Worker
	DBPath        string
	HTTPPort      string
	RPCPort       string
	ContentDir    string
	WorkDir       string
	LogLevel      string
	MaxJobLogSize int
	ScheduleRate  time.Duration
	ScheduleChunk int
	// How long to wait for a worker ping before marking it as dead
	WorkerPingTimeout time.Duration
	// How long to wait for worker initialization before marking it dead
	WorkerInitTimeout time.Duration
}

// DefaultConfig returns configuration with simple defaults.
func DefaultConfig() Config {
	workDir := "tes-work-dir"
	return Config{
		ServerAddress: "localhost:9090",
		DBPath:        path.Join(workDir, "tes_task.db"),
		HTTPPort:      "8000",
		RPCPort:       "9090",
		ContentDir:    defaultContentDir(),
		WorkDir:       workDir,
		LogLevel:      "debug",
		Scheduler:     "local",
		Schedulers: Schedulers{
			Local: LocalScheduler{},
			GCE: GCEScheduler{
				Weights: Weights{
					"startup time": 1.0,
				},
			},
		},
		Worker:            WorkerDefaultConfig(),
		MaxJobLogSize:     10000,
		ScheduleRate:      time.Second,
		ScheduleChunk:     10,
		WorkerPingTimeout: time.Minute,
		WorkerInitTimeout: time.Minute * 5,
	}
}

// Worker contains worker configuration.
type Worker struct {
	ID string
	// Address of the scheduler, e.g. "1.2.3.4:9090"
	ServerAddress string
	// Directory to write job files to
	WorkDir string
	// How long (seconds) to wait before tearing down an inactive worker
	// Default, -1, indicates to tear down the worker immediately after completing
	// its job
	Timeout time.Duration
	// How often the worker sends update requests to the server
	UpdateRate time.Duration
	// How often the worker sends job log updates
	LogUpdateRate time.Duration
	TrackerRate   time.Duration
	LogTailSize   int64
	Storage       []*StorageConfig
	LogPath       string
	LogLevel      string
	Resources     *pbr.Resources
	// Timeout duration for UpdateWorker() and UpdateJobLogs() RPC calls
	UpdateTimeout time.Duration
	Metadata      map[string]string
}

// WorkerDefaultConfig returns simple, default worker configuration.
func WorkerDefaultConfig() Worker {
	return Worker{
		ServerAddress: "localhost:9090",
		WorkDir:       "tes-work-dir",
		Timeout:       -1,
		// TODO these get reset to zero when not found in yaml?
		UpdateRate:    time.Second * 5,
		LogUpdateRate: time.Second * 5,
		TrackerRate:   time.Second * 5,
		LogTailSize:   10000,
		LogLevel:      "debug",
		UpdateTimeout: time.Second,
	}
}

// ToYaml formats the configuration into YAML and returns the bytes.
func (c Worker) ToYaml() []byte {
	// TODO handle error
	yamlstr, _ := yaml.Marshal(c)
	return yamlstr
}

// ToYamlFile writes the configuration to a YAML file.
func (c Worker) ToYamlFile(p string) {
	// TODO handle error
	ioutil.WriteFile(p, c.ToYaml(), 0600)
}

// ToYamlTempFile writes the configuration to a YAML temp. file.
func (c Worker) ToYamlTempFile(name string) (string, func()) {
	// I'm creating a temp. directory instead of a temp. file so that
	// the file can have an expected name. This is helpful for the HTCondor scheduler.
	tmpdir, _ := ioutil.TempDir("", "")

	cleanup := func() {
		os.RemoveAll(tmpdir)
	}

	p := filepath.Join(tmpdir, name)
	c.ToYamlFile(p)
	return p, cleanup
}

func defaultContentDir() string {
	// TODO this depends on having the entire repo available
	//      which prevents us from releasing a single binary.
	//      Not the worst, but maybe there's a good way to make it optional.
	// TODO handle error
	dir, _ := filepath.Abs(os.Args[0])
	return filepath.Join(dir, "..", "..", "share")
}

// ParseConfigFile parses a TES config file, which is formatted in YAML,
// and returns a Config struct.
func ParseConfigFile(path string, doc interface{}) error {
	source, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	err = yaml.Unmarshal(source, &doc)
	if err != nil {
		return err
	}
	return nil
}

// LoadConfigOrExit tries to load the config from the given file.
// If the file cannot be loaded, os.Exit() is called.
func LoadConfigOrExit(relpath string, config interface{}) {
	var err error
	if relpath != "" {
		var abspath string
		abspath, err = filepath.Abs(relpath)
		if err != nil {
			log.Error("Failure reading config", "path", abspath, "error", err)
			os.Exit(1)
		}
		log.Info("Using config file", "path", abspath)
		err = ParseConfigFile(abspath, &config)
		if err != nil {
			log.Error("Failure reading config", "path", abspath, "error", err)
			os.Exit(1)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Andrew E. Bruno
//
// 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 khttp is a transport that authenticates all outgoing requests using
// SPNEGO (negotiate authentication) http://tools.ietf.org/html/rfc4559.
package khttp

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/ubccr/kerby"
)

var (
	negotiateHeader       = "Negotiate"
	wwwAuthenticateHeader = "WWW-Authenticate"
	authorizationHeader   = "Authorization"
)

// HTTP client transport that authenticates all outgoing
// requests using SPNEGO. Implements the http.RoundTripper interface
type Transport struct {
	// keytab file to use
	KeyTab string
	// principal
	Principal string
	// Next specifies the next transport to be used or http.DefaultTransport if nil.
	Next http.RoundTripper
}

// RoundTrip executes a single HTTP transaction performing SPNEGO negotiate
// authentication.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	if len(t.KeyTab) > 0 {
		os.Setenv("KRB5_CLIENT_KTNAME", t.KeyTab)
	}
	host, _, err := net.SplitHostPort(req.URL.Host)
	if err != nil {
		host = req.URL.Host
	}
	service := fmt.Sprintf("HTTP@%s", host)
	kc := new(kerby.KerbClient)
	err = kc.Init(service, t.Principal)
	if err != nil {
		return nil, err
	}
	defer kc.Clean()

	err = kc.Step("")
	if err != nil {
		return nil, err
	}

	req.Header.Set(authorizationHeader, negotiateHeader+" "+kc.Response())

	tr := t.Next
	if tr == nil {
		tr = http.DefaultTransport
		if tr == nil {
			return nil, errors.New("khttp: no Next transport or DefaultTransport")
		}
	}

	resp, err := tr.RoundTrip(req)
	if err != nil {
		return nil, err
	}

	authReply := strings.Split(resp.Header.Get(wwwAuthenticateHeader), " ")
	if len(authReply) != 2 || authReply[0] != negotiateHeader {
		return nil, errors.New("khttp: server replied with invalid www-authenticate header")
	}

	// Authenticate the reply from the server
	err = kc.Step(authReply[1])
	if err != nil {
		return nil, err
	}

	return resp, nil
}
<commit_msg>http.go: Make negotiateHeader match case insensitive<commit_after>// Copyright 2015 Andrew E. Bruno
//
// 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 khttp is a transport that authenticates all outgoing requests using
// SPNEGO (negotiate authentication) http://tools.ietf.org/html/rfc4559.
package khttp

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/ubccr/kerby"
)

var (
	negotiateHeader       = "negotiate"
	wwwAuthenticateHeader = "WWW-Authenticate"
	authorizationHeader   = "Authorization"
)

// HTTP client transport that authenticates all outgoing
// requests using SPNEGO. Implements the http.RoundTripper interface
type Transport struct {
	// keytab file to use
	KeyTab string
	// principal
	Principal string
	// Next specifies the next transport to be used or http.DefaultTransport if nil.
	Next http.RoundTripper
}

// RoundTrip executes a single HTTP transaction performing SPNEGO negotiate
// authentication.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	if len(t.KeyTab) > 0 {
		os.Setenv("KRB5_CLIENT_KTNAME", t.KeyTab)
	}
	host, _, err := net.SplitHostPort(req.URL.Host)
	if err != nil {
		host = req.URL.Host
	}
	service := fmt.Sprintf("HTTP@%s", host)
	kc := new(kerby.KerbClient)
	err = kc.Init(service, t.Principal)
	if err != nil {
		return nil, err
	}
	defer kc.Clean()

	err = kc.Step("")
	if err != nil {
		return nil, err
	}

	req.Header.Set(authorizationHeader, negotiateHeader+" "+kc.Response())

	tr := t.Next
	if tr == nil {
		tr = http.DefaultTransport
		if tr == nil {
			return nil, errors.New("khttp: no Next transport or DefaultTransport")
		}
	}

	resp, err := tr.RoundTrip(req)
	if err != nil {
		return nil, err
	}

	authReply := strings.Split(resp.Header.Get(wwwAuthenticateHeader), " ")
	if len(authReply) != 2 || strings.ToLower(authReply[0]) != negotiateHeader {
		return nil, errors.New("khttp: server replied with invalid www-authenticate header")
	}

	// Authenticate the reply from the server
	err = kc.Step(authReply[1])
	if err != nil {
		return nil, err
	}

	return resp, nil
}
<|endoftext|>


<commit_before>package kite

import (
	"github.com/cloudfoundry/gosigar"
	"koding/newkite/protocol"
	"os/exec"
	"runtime"
	"strings"
)

type Status struct{}

type Info struct {
	State       string `json:"state"`
	DiskUsage   string `json:"diskUsage"`
	DiskTotal   string `json:"diskTotal"`
	MemoryUsage uint64 `json:"memoryUsage"`
	MemoryTotal uint64 `json:"memoryTotal"`
}

type memory struct {
	Usage uint64 `json:"memoryUsage"`
	Total uint64 `json:"memoryTotal"`
}

type disk struct {
	Usage string `json:"diskUsage"`
	Total string `json:"diskTotal"`
}

func memoryStats() *memory {
	m := new(memory)
	mem := sigar.Mem{}
	if err := mem.Get(); err == nil {
		m.Usage = mem.ActualUsed / 1024 / 1024
		m.Total = mem.Total / 1024 / 1024
	}

	return m
}

func diskStats() *disk {
	if runtime.GOOS != "darwin" {
		return nil // only darwin
	}

	out, _ := exec.Command("bash", "-c", "df -H | grep '\\/dev\\/'|  awk '{print $2, $3}'").CombinedOutput()
	diskStats := strings.Split(strings.TrimSpace(string(out)), " ")

	d := new(disk)
	d.Total = diskStats[0]
	d.Usage = diskStats[1]
	return d
}

func (Status) Info(r *protocol.KiteDnodeRequest, result *Info) error {
	disk := diskStats()
	mem := memoryStats()

	info := &Info{
		State:       "Running", // needed for client side compatibility
		DiskUsage:   disk.Usage,
		DiskTotal:   disk.Total,
		MemoryUsage: mem.Usage,
		MemoryTotal: mem.Total,
	}

	*result = *info
	return nil
}
<commit_msg>kite/stats: fixes for client side compatibility<commit_after>package kite

import (
	"github.com/cloudfoundry/gosigar"
	"koding/newkite/protocol"
	"os/exec"
	"runtime"
	"strings"
)

type Status struct{}

type Info struct {
	State       string `json:"state"`
	DiskUsage   string `json:"diskUsage"`
	DiskTotal   string `json:"diskTotal"`
	MemoryUsage uint64 `json:"memoryUsage"`
	MemoryTotal uint64 `json:"totalMemoryLimit"`
}

type memory struct {
	Usage uint64 `json:"memoryUsage"`
	Total uint64 `json:"memoryTotal"`
}

type disk struct {
	Usage string `json:"diskUsage"`
	Total string `json:"diskTotal"`
}

func memoryStats() *memory {
	m := new(memory)
	mem := sigar.Mem{}
	if err := mem.Get(); err == nil {
		m.Usage = mem.ActualUsed
		m.Total = mem.Total
	}

	return m
}

func diskStats() *disk {
	if runtime.GOOS != "darwin" {
		return nil // only darwin
	}

	out, _ := exec.Command("bash", "-c", "df -H | grep '\\/dev\\/'|  awk '{print $2, $3}'").CombinedOutput()
	diskStats := strings.Split(strings.TrimSpace(string(out)), " ")

	d := new(disk)
	d.Total = diskStats[0]
	d.Usage = diskStats[1]
	return d
}

func (Status) Info(r *protocol.KiteDnodeRequest, result *Info) error {
	disk := diskStats()
	mem := memoryStats()

	info := &Info{
		State:       "RUNNING", // needed for client side compatibility
		DiskUsage:   disk.Usage,
		DiskTotal:   disk.Total,
		MemoryUsage: mem.Usage,
		MemoryTotal: mem.Total,
	}

	*result = *info
	return nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2006-2011 Philipp Meinen <philipp@bind.ch>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
 * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/PhiCode/l10n_check/config/properties"
	"github.com/PhiCode/l10n_check/validate"
)

type result struct {
	file  string
	props *properties.Properties
	valid *validate.Results
}

var (
	verbose *bool = flag.Bool("v", false, "enable verbose mode")
	nowarn  *bool = flag.Bool("nowarn", false, "do not print warnings")
)

func main() {
	flag.Usage = func() {
		fmt.Printf("usage: %s [-v] <file-name> [<file-name> ...]\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(1)
	}
	flag.Parse()
	if flag.NArg() < 1 {
		flag.Usage()
	}
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("caught error:", err)
			os.Exit(3)
		}
	}()

	anyFault := false
	args := flag.Args()
	results := make([]result, 0, len(args))
	for _, file := range args {
		props, valid, err := properties.ReadAndParse(file)
		if err != nil {
			fmt.Println(err)
			anyFault = true
			continue
		}
		r := result{file, props, valid}
		results = append(results, r)
		//fmt.Printf(r.String())
	}

	for _, result := range results {
		fmt.Printf("%s: %d keys\n", result.file, len(result.props.ByKey))
		if *verbose {
			result.props.PrintAll("\t")
		}
	}

	fmt.Println()

	for _, result := range results {
		n := result.valid.Print(*nowarn)
		anyFault = anyFault || n > 0
	}

	if len(results) > 1 {
		master := results[0]
		for _, other := range results[1:] {
			n := properties.AnalyzeAll(master.props, other.props)
			anyFault = anyFault || n > 0
		}
	}

	if anyFault {
		os.Exit(2)
	}
	os.Exit(0)
}

func (r *result) String() string {
	return fmt.Sprintf("file: %s\nprops:\n%s\nvalid:\n%s\n", r.file, r.props, r.valid)
}
<commit_msg>minor cleanup of unneeded code<commit_after>/*
 * Copyright (c) 2006-2011 Philipp Meinen <philipp@bind.ch>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
 * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/PhiCode/l10n_check/config/properties"
	"github.com/PhiCode/l10n_check/validate"
)

type result struct {
	file  string
	props *properties.Properties
	valid *validate.Results
}

var (
	verbose *bool = flag.Bool("v", false, "enable verbose mode")
	nowarn  *bool = flag.Bool("nowarn", false, "do not print warnings")
)

func main() {
	flag.Usage = func() {
		fmt.Printf("usage: %s [-v] <file-name> [<file-name> ...]\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(1)
	}
	flag.Parse()
	if flag.NArg() < 1 {
		flag.Usage()
	}

	anyFault := false
	args := flag.Args()
	results := make([]result, 0, len(args))
	for _, file := range args {
		props, valid, err := properties.ReadAndParse(file)
		if err != nil {
			fmt.Println(err)
			anyFault = true
			continue
		}
		r := result{file, props, valid}
		results = append(results, r)
	}

	for _, result := range results {
		fmt.Printf("%s: %d keys\n", result.file, len(result.props.ByKey))
		if *verbose {
			result.props.PrintAll("\t")
		}
	}

	fmt.Println()

	for _, result := range results {
		n := result.valid.Print(*nowarn)
		anyFault = anyFault || n > 0
	}

	if len(results) > 1 {
		master := results[0]
		for _, other := range results[1:] {
			n := properties.AnalyzeAll(master.props, other.props)
			anyFault = anyFault || n > 0
		}
	}

	if anyFault {
		os.Exit(2)
	}
}

func (r *result) String() string {
	return fmt.Sprintf("file: %s\nprops:\n%s\nvalid:\n%s\n", r.file, r.props, r.valid)
}
<|endoftext|>


<commit_before>package tui

import (
	"image"
	"testing"
)

var labelTests = []struct {
	test     string
	setup    func() *Label
	size     image.Point
	sizeHint image.Point
}{
	{
		test: "Empty",
		setup: func() *Label {
			return NewLabel("")
		},
		size:     image.Point{100, 100},
		sizeHint: image.Point{0, 1},
	},
	{
		test: "Single word",
		setup: func() *Label {
			return NewLabel("test")
		},
		size:     image.Point{100, 100},
		sizeHint: image.Point{4, 1},
	},
	{
		test: "Wide word",
		setup: func() *Label {
			return NewLabel("あäa")
		},
		size:     image.Point{100, 100},
		sizeHint: image.Point{4, 1},
	},
}

func TestLabel_Size(t *testing.T) {
	for _, tt := range labelTests {
		tt := tt
		t.Run(tt.test, func(t *testing.T) {
			t.Parallel()

			l := tt.setup()
			l.Resize(image.Point{100, 100})

			if got := l.Size(); got != tt.size {
				t.Errorf("l.Size() = %s; want = %s", got, tt.size)
			}
			if got := l.SizeHint(); got != tt.sizeHint {
				t.Errorf("l.SizeHint() = %s; want = %s", got, tt.sizeHint)
			}
		})
	}
}

var drawLabelTests = []struct {
	test  string
	setup func() *Label
	want  string
}{
	{
		test: "Simple label",
		setup: func() *Label {
			return NewLabel("test")
		},
		want: `
test......
..........
..........
..........
..........
`,
	},
	{
		test: "Word wrap",
		setup: func() *Label {
			l := NewLabel("this will wrap")
			l.SetWordWrap(true)
			l.SetSizePolicy(Expanding, Expanding)
			return l
		},
		want: `
this will.
wrap......
..........
..........
..........
`,
	},
}

func TestLabel_Draw(t *testing.T) {
	for _, tt := range drawLabelTests {
		surface := NewTestSurface(10, 5)

		painter := NewPainter(surface, NewTheme())
		painter.Repaint(tt.setup())

		if surface.String() != tt.want {
			t.Errorf("got = \n%s\n\nwant = \n%s", surface.String(), tt.want)
		}
	}
}
<commit_msg>Add failing test: "null set" character increases lines<commit_after>package tui

import (
	"image"
	"testing"
)

var labelTests = []struct {
	test     string
	setup    func() *Label
	size     image.Point
	sizeHint image.Point
}{
	{
		test: "Empty",
		setup: func() *Label {
			return NewLabel("")
		},
		size:     image.Point{100, 100},
		sizeHint: image.Point{0, 1},
	},
	{
		test: "Single word",
		setup: func() *Label {
			return NewLabel("test")
		},
		size:     image.Point{100, 100},
		sizeHint: image.Point{4, 1},
	},
	{
		test: "Wide word",
		setup: func() *Label {
			return NewLabel("あäa")
		},
		size:     image.Point{100, 100},
		sizeHint: image.Point{4, 1},
	},
	{
		test: "Unicode only",
		setup: func() *Label {
			return NewLabel("∅")
		},
		size:     image.Point{100, 100},
		sizeHint: image.Point{1, 1},
	},
	{
		test: "Tall string",
		setup: func() *Label {
			return NewLabel("Null set: ∅")
		},
		size:     image.Point{100, 100},
		sizeHint: image.Point{11, 1},
	},
}

func TestLabel_Size(t *testing.T) {
	for _, tt := range labelTests {
		tt := tt
		t.Run(tt.test, func(t *testing.T) {
			t.Parallel()

			l := tt.setup()
			l.Resize(image.Point{100, 100})

			if got := l.Size(); got != tt.size {
				t.Errorf("l.Size() = %s; want = %s", got, tt.size)
			}
			if got := l.SizeHint(); got != tt.sizeHint {
				t.Errorf("l.SizeHint() = %s; want = %s", got, tt.sizeHint)
			}
		})
	}
}

var drawLabelTests = []struct {
	test  string
	setup func() *Label
	want  string
}{
	{
		test: "Simple label",
		setup: func() *Label {
			return NewLabel("test")
		},
		want: `
test......
..........
..........
..........
..........
`,
	},
	{
		test: "Word wrap",
		setup: func() *Label {
			l := NewLabel("this will wrap")
			l.SetWordWrap(true)
			l.SetSizePolicy(Expanding, Expanding)
			return l
		},
		want: `
this will.
wrap......
..........
..........
..........
`,
	},
}

func TestLabel_Draw(t *testing.T) {
	for _, tt := range drawLabelTests {
		surface := NewTestSurface(10, 5)

		painter := NewPainter(surface, NewTheme())
		painter.Repaint(tt.setup())

		if surface.String() != tt.want {
			t.Errorf("got = \n%s\n\nwant = \n%s", surface.String(), tt.want)
		}
	}
}
<|endoftext|>


<commit_before>package lclip

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/mitchellh/go-homedir"
)

var tempPath string

func TestMain(m *testing.M) {
	f, err := ioutil.TempFile(os.TempDir(), "test")
	if err != nil {
		fmt.Fprintln(os.Stderr, "lclip_test:", err)
		return
	}
	f.Close()
	tempPath = f.Name()

	e := m.Run()
	defer os.Exit(e)

	os.Remove(tempPath)
}

func TestDefaultPath(t *testing.T) {
	h, err := homedir.Dir()
	if err != nil {
		t.Fatal(err)
	}
	expect := filepath.Join(h, ".lclip.json")
	actual, err := DefaultPath()
	if err != nil {
		t.Errorf("DefaultPath returns %q; want nil", err)
	}
	if actual != expect {
		t.Errorf("DefaultPath = %q; want %q",
			actual, expect)
	}
}

type GetTextTest struct {
	Src string
	Dst string
}

var indexTestsGetText = []GetTextTest{
	{Src: "foo", Dst: "bar"},
	{Src: "hoge", Dst: "piyo"},
}

func TestGetText(t *testing.T) {
	w := bytes.NewBuffer(make([]byte, 0))
	m := make(map[string]string)
	for _, test := range indexTestsGetText {
		m[test.Src] = test.Dst
	}
	if err := json.NewEncoder(w).Encode(m); err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(tempPath, w.Bytes(), 0644); err != nil {
		t.Fatal(err)
	}

	c, err := NewClipboard(tempPath)
	if err != nil {
		t.Errorf("NewClipboard returns %q; want nil", err)
	}
	for _, test := range indexTestsGetText {
		expect := test.Dst
		actual := c.Get(test.Src)
		if actual != expect {
			t.Errorf("Get(%q) = %q; want %q",
				test.Src, actual, expect)
		}
	}
}

type SetTextTest struct {
	Label string
	Data  string
}

var indexTestsSetText = []SetTextTest{
	{Label: "a", Data: "aaa"},
	{Label: "abc", Data: "def"},
	{Label: "", Data: ""},
}

func TestSetText(t *testing.T) {
	if err := ioutil.WriteFile(tempPath, []byte(`{}`), 0644); err != nil {
		t.Fatal(err)
	}

	c, err := NewClipboard(tempPath)
	if err != nil {
		t.Errorf("NewClipboard returns %q; want nil", err)
	}
	for _, test := range indexTestsSetText {
		c.Set(test.Label, test.Data)
		expect := test.Data
		actual := c.Get(test.Label)
		if actual != expect {
			t.Errorf("after Set(%q, %q), Get(%q) = %q; want %q",
				test.Label, test.Data,
				test.Label, actual, expect)
		}
	}
}
<commit_msg>Add test for Close<commit_after>package lclip

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/mitchellh/go-homedir"
)

var tempPath string

func TestMain(m *testing.M) {
	f, err := ioutil.TempFile(os.TempDir(), "test")
	if err != nil {
		fmt.Fprintln(os.Stderr, "lclip_test:", err)
		return
	}
	f.Close()
	tempPath = f.Name()

	e := m.Run()
	defer os.Exit(e)

	os.Remove(tempPath)
}

func TestDefaultPath(t *testing.T) {
	h, err := homedir.Dir()
	if err != nil {
		t.Fatal(err)
	}
	expect := filepath.Join(h, ".lclip.json")
	actual, err := DefaultPath()
	if err != nil {
		t.Errorf("DefaultPath returns %q; want nil", err)
	}
	if actual != expect {
		t.Errorf("DefaultPath = %q; want %q",
			actual, expect)
	}
}

type GetTextTest struct {
	Src string
	Dst string
}

var indexTestsGetText = []GetTextTest{
	{Src: "foo", Dst: "bar"},
	{Src: "hoge", Dst: "piyo"},
}

func TestGetText(t *testing.T) {
	w := bytes.NewBuffer(make([]byte, 0))
	m := make(map[string]string)
	for _, test := range indexTestsGetText {
		m[test.Src] = test.Dst
	}
	if err := json.NewEncoder(w).Encode(m); err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(tempPath, w.Bytes(), 0644); err != nil {
		t.Fatal(err)
	}

	c, err := NewClipboard(tempPath)
	if err != nil {
		t.Errorf("NewClipboard returns %q; want nil", err)
	}
	for _, test := range indexTestsGetText {
		expect := test.Dst
		actual := c.Get(test.Src)
		if actual != expect {
			t.Errorf("Get(%q) = %q; want %q",
				test.Src, actual, expect)
		}
	}
}

type SetTextTest struct {
	Label string
	Data  string
}

var indexTestsSetText = []SetTextTest{
	{Label: "a", Data: "aaa"},
	{Label: "abc", Data: "def"},
	{Label: "", Data: ""},
}

func TestSetText(t *testing.T) {
	if err := ioutil.WriteFile(tempPath, []byte(`{}`), 0644); err != nil {
		t.Fatal(err)
	}

	c, err := NewClipboard(tempPath)
	if err != nil {
		t.Errorf("NewClipboard returns %q; want nil", err)
	}
	for _, test := range indexTestsSetText {
		c.Set(test.Label, test.Data)
		expect := test.Data
		actual := c.Get(test.Label)
		if actual != expect {
			t.Errorf("after Set(%q, %q), Get(%q) = %q; want %q",
				test.Label, test.Data,
				test.Label, actual, expect)
		}
	}
}

func TestSaveText(t *testing.T) {
	if err := ioutil.WriteFile(tempPath, []byte(`{}`), 0644); err != nil {
		t.Fatal(err)
	}

	k, v := "key", "value"
	{
		c, err := NewClipboard(tempPath)
		if err != nil {
			t.Errorf("NewClipboard returns %q; want nil", err)
		}
		c.Set(k, v)
		if err := c.Close(); err != nil {
			t.Error("Close returns %q; want nil", err)
		}
	}
	{
		c, err := NewClipboard(tempPath)
		if err != nil {
			t.Errorf("NewClipboard returns %q; want nil", err)
		}
		expect := v
		actual := c.Get(k)
		if actual != expect {
			t.Errorf("Get(%q) = %q; want %q",
				k, actual, expect)
		}
	}
}
<|endoftext|>


<commit_before>package goship

import (
	"fmt"
	"log"
	"path/filepath"
	"sort"

	"github.com/coreos/go-etcd/etcd"
)

// gitHubPaginationLimit is the default pagination limit for requests to the GitHub API that return multiple items.
const (
	gitHubPaginationLimit = 30
	pivotalCommentURL     = "https://www.pivotaltracker.com/services/v5/projects/%s/stories/%s/comments"
	gitHubAPITokenEnvVar  = "GITHUB_API_TOKEN"
)

// Host stores information on a host, such as URI and the latest commit revision.
type Host struct {
	URI             string
	LatestCommit    string
	GitHubCommitURL string
	GitHubDiffURL   string
	ShortCommitHash string
}

// Environment stores information about an individual environment, such as its name and whether it is deployable.
type Environment struct {
	Name               string
	Deploy             string
	RepoPath           string
	Hosts              []Host
	Branch             string
	Revision           string
	LatestGitHubCommit string
	IsDeployable       bool
}

// Project stores information about a GitHub project, such as its GitHub URL and repo name.
type Project struct {
	Name         string
	GitHubURL    string
	RepoName     string
	RepoOwner    string
	Environments []Environment
}

// Sort interface for sorting projects
type ByName []Project

func (slice ByName) Len() int           { return len(slice) }
func (slice ByName) Less(i, j int) bool { return slice[i].Name < slice[j].Name }
func (slice ByName) Swap(i, j int)      { slice[i], slice[j] = slice[j], slice[i] }

// gitHubCommitURL takes a project and returns the GitHub URL for its latest commit hash.
func (h *Host) LatestGitHubCommitURL(p Project) string {
	return fmt.Sprintf("%s/commit/%s", p.GitHubURL, h.LatestCommit)
}

// gitHubDiffURL takes a project and an environment and returns the GitHub diff URL
// for the latest commit on the host compared to the latest commit on GitHub.
func (h *Host) LatestGitHubDiffURL(p Project, e Environment) string {
	var s string
	if h.LatestCommit != e.LatestGitHubCommit {
		s = fmt.Sprintf("%s/compare/%s...%s", p.GitHubURL, h.LatestCommit, e.LatestGitHubCommit)
	}
	return s
}

// Deployable returns true if the latest commit for any of the hosts in an environment
// differs from the latest commit on GitHub, and false if all of the commits match.
func (e *Environment) Deployable() bool {
	for _, h := range e.Hosts {
		if e.LatestGitHubCommit != h.LatestCommit {
			return true
		}
	}
	return false
}

// ShortCommitHash returns a shortened version of the latest commit hash on a host.
func (h *Host) LatestShortCommitHash() string {
	if len(h.LatestCommit) == 0 {
		return ""
	}
	return h.LatestCommit[:7]
}

// config contains the information from config.yml.
type Config struct {
	Projects   []Project
	DeployUser string
	Notify     string
	Pivotal    *PivotalConfiguration
}

type PivotalConfiguration struct {
	Project string
	Token   string
}

type ETCDInterface interface {
	Get(string, bool, bool) (*etcd.Response, error)
}

// ProjectFromName takes a project name as a string and returns
// a Project by that name if it can find one.
func ProjectFromName(projects []Project, projectName string) (*Project, error) {
	for _, project := range projects {
		if project.Name == projectName {
			return &project, nil
		}
	}
	return nil, fmt.Errorf("No project found: %s", projectName)
}

// EnvironmentFromName takes an environment and project name as a string and returns
// an Environment by the given environment name under a project with the given
// project name if it can find one.
func EnvironmentFromName(projects []Project, projectName, environmentName string) (*Environment, error) {
	p, err := ProjectFromName(projects, projectName)
	if err != nil {
		return nil, err
	}
	for _, environment := range p.Environments {
		if environment.Name == environmentName {
			return &environment, nil
		}
	}
	return nil, fmt.Errorf("No environment found: %s", environmentName)
}

// connects to ETCD and returns the appropriate structs and strings.
func ParseETCD(client ETCDInterface) (c Config, err error) {
	log.Println("Parsinf ETCD")
	baseInfo, err := client.Get("/", false, false)
	if err != nil {
		return c, err
	}
	deployUser := ""
	pivotalProject := ""
	token := ""
	notify := ""
	for _, b := range baseInfo.Node.Nodes {
		switch filepath.Base(b.Key) {
		case "deploy_user":
			deployUser = b.Value
		case "pivotal_project":
			pivotalProject = filepath.Base(b.Value)
		case "token":
			token = filepath.Base(b.Value)
		case "notify":
			notify = filepath.Base(b.Value)
		}
	}

	allProjects := []Project{}
	//  Get Projects //
	projectNodes, err := client.Get("/projects", false, false)
	if err != nil {
		return c, err
	}
	for _, p := range projectNodes.Node.Nodes {
		name := filepath.Base(p.Key)
		projectNode, err := client.Get("/projects/"+name, false, false)
		if err != nil {
			return c, err
		}
		projectInfo := projectNode.Node.Nodes
		repoOwner := ""
		repoName := ""
		for _, k := range projectInfo {
			switch filepath.Base(k.Key) {
			case "repo_owner":
				repoOwner = filepath.Base(k.Value)
			case "repo_name":
				repoName = filepath.Base(k.Value)
			}
		}
		githubURL := fmt.Sprintf("https://github.com/%s/%s", repoOwner, repoName)
		proj := Project{Name: name, GitHubURL: githubURL, RepoName: repoName, RepoOwner: repoOwner}
		environments, err := client.Get("/projects/"+name+"/environments", false, false)
		if err != nil {
			return c, err
		}
		allEnvironments := []Environment{}
		for _, e := range environments.Node.Nodes {
			envSettings, err := client.Get("/projects/"+name+"/environments/"+filepath.Base(e.Key), false, false)
			envName := filepath.Base(e.Key)
			revision := "head"
			branch := "master"
			deploy := ""
			repoPath := ""
			for _, n := range envSettings.Node.Nodes {
				switch filepath.Base(n.Key) {
				case "revision":
					revision = n.Value
				case "branch":
					branch = n.Value
				case "deploy":
					deploy = n.Value
				case "repo_path":
					repoPath = n.Value
				}
			}
			//  Get Hosts per Environment.
			hosts, err := client.Get("/projects/"+name+"/environments/"+envName+"/hosts", false, false)
			if err != nil {
				return c, err
			}
			allHosts := []Host{}
			for _, h := range hosts.Node.Nodes {
				host := Host{URI: filepath.Base(h.Key)}
				allHosts = append(allHosts, host)
			}
			env := Environment{Name: envName, Deploy: deploy, RepoPath: repoPath, Branch: branch, Revision: revision}
			env.Hosts = allHosts
			allEnvironments = append(allEnvironments, env)
		}
		proj.Environments = allEnvironments
		allProjects = append(allProjects, proj)
		log.Printf("PROJECTS: %s", allProjects)
	}
	piv := new(PivotalConfiguration)
	piv.Project = pivotalProject
	piv.Token = token
	sort.Sort(ByName(allProjects))
	c.Projects = allProjects
	c.DeployUser = deployUser
	c.Notify = notify
	c.Pivotal = piv

	return c, err
}
<commit_msg>Remove debug add sort<commit_after>package goship

import (
	"fmt"
	"path/filepath"
	"sort"

	"github.com/coreos/go-etcd/etcd"
)

// gitHubPaginationLimit is the default pagination limit for requests to the GitHub API that return multiple items.
const (
	gitHubPaginationLimit = 30
	pivotalCommentURL     = "https://www.pivotaltracker.com/services/v5/projects/%s/stories/%s/comments"
	gitHubAPITokenEnvVar  = "GITHUB_API_TOKEN"
)

// Host stores information on a host, such as URI and the latest commit revision.
type Host struct {
	URI             string
	LatestCommit    string
	GitHubCommitURL string
	GitHubDiffURL   string
	ShortCommitHash string
}

// Environment stores information about an individual environment, such as its name and whether it is deployable.
type Environment struct {
	Name               string
	Deploy             string
	RepoPath           string
	Hosts              []Host
	Branch             string
	Revision           string
	LatestGitHubCommit string
	IsDeployable       bool
}

// Project stores information about a GitHub project, such as its GitHub URL and repo name.
type Project struct {
	Name         string
	GitHubURL    string
	RepoName     string
	RepoOwner    string
	Environments []Environment
}

// Sort interface for sorting projects
type ByName []Project

func (slice ByName) Len() int           { return len(slice) }
func (slice ByName) Less(i, j int) bool { return slice[i].Name < slice[j].Name }
func (slice ByName) Swap(i, j int)      { slice[i], slice[j] = slice[j], slice[i] }

// gitHubCommitURL takes a project and returns the GitHub URL for its latest commit hash.
func (h *Host) LatestGitHubCommitURL(p Project) string {
	return fmt.Sprintf("%s/commit/%s", p.GitHubURL, h.LatestCommit)
}

// gitHubDiffURL takes a project and an environment and returns the GitHub diff URL
// for the latest commit on the host compared to the latest commit on GitHub.
func (h *Host) LatestGitHubDiffURL(p Project, e Environment) string {
	var s string
	if h.LatestCommit != e.LatestGitHubCommit {
		s = fmt.Sprintf("%s/compare/%s...%s", p.GitHubURL, h.LatestCommit, e.LatestGitHubCommit)
	}
	return s
}

// Deployable returns true if the latest commit for any of the hosts in an environment
// differs from the latest commit on GitHub, and false if all of the commits match.
func (e *Environment) Deployable() bool {
	for _, h := range e.Hosts {
		if e.LatestGitHubCommit != h.LatestCommit {
			return true
		}
	}
	return false
}

// ShortCommitHash returns a shortened version of the latest commit hash on a host.
func (h *Host) LatestShortCommitHash() string {
	if len(h.LatestCommit) == 0 {
		return ""
	}
	return h.LatestCommit[:7]
}

// config contains the information from config.yml.
type Config struct {
	Projects   []Project
	DeployUser string
	Notify     string
	Pivotal    *PivotalConfiguration
}

type PivotalConfiguration struct {
	Project string
	Token   string
}

type ETCDInterface interface {
	Get(string, bool, bool) (*etcd.Response, error)
}

// ProjectFromName takes a project name as a string and returns
// a Project by that name if it can find one.
func ProjectFromName(projects []Project, projectName string) (*Project, error) {
	for _, project := range projects {
		if project.Name == projectName {
			return &project, nil
		}
	}
	return nil, fmt.Errorf("No project found: %s", projectName)
}

// EnvironmentFromName takes an environment and project name as a string and returns
// an Environment by the given environment name under a project with the given
// project name if it can find one.
func EnvironmentFromName(projects []Project, projectName, environmentName string) (*Environment, error) {
	p, err := ProjectFromName(projects, projectName)
	if err != nil {
		return nil, err
	}
	for _, environment := range p.Environments {
		if environment.Name == environmentName {
			return &environment, nil
		}
	}
	return nil, fmt.Errorf("No environment found: %s", environmentName)
}

// connects to ETCD and returns the appropriate structs and strings.
func ParseETCD(client ETCDInterface) (c Config, err error) {
	baseInfo, err := client.Get("/", false, false)
	if err != nil {
		return c, err
	}
	deployUser := ""
	pivotalProject := ""
	token := ""
	notify := ""
	for _, b := range baseInfo.Node.Nodes {
		switch filepath.Base(b.Key) {
		case "deploy_user":
			deployUser = b.Value
		case "pivotal_project":
			pivotalProject = filepath.Base(b.Value)
		case "token":
			token = filepath.Base(b.Value)
		case "notify":
			notify = filepath.Base(b.Value)
		}
	}

	allProjects := []Project{}
	//  Get Projects //
	projectNodes, err := client.Get("/projects", false, false)
	if err != nil {
		return c, err
	}
	for _, p := range projectNodes.Node.Nodes {
		name := filepath.Base(p.Key)
		projectNode, err := client.Get("/projects/"+name, false, false)
		if err != nil {
			return c, err
		}
		projectInfo := projectNode.Node.Nodes
		repoOwner := ""
		repoName := ""
		for _, k := range projectInfo {
			switch filepath.Base(k.Key) {
			case "repo_owner":
				repoOwner = filepath.Base(k.Value)
			case "repo_name":
				repoName = filepath.Base(k.Value)
			}
		}
		githubURL := fmt.Sprintf("https://github.com/%s/%s", repoOwner, repoName)
		proj := Project{Name: name, GitHubURL: githubURL, RepoName: repoName, RepoOwner: repoOwner}
		environments, err := client.Get("/projects/"+name+"/environments", false, false)
		if err != nil {
			return c, err
		}
		allEnvironments := []Environment{}
		for _, e := range environments.Node.Nodes {
			envSettings, err := client.Get("/projects/"+name+"/environments/"+filepath.Base(e.Key), false, false)
			envName := filepath.Base(e.Key)
			revision := "head"
			branch := "master"
			deploy := ""
			repoPath := ""
			for _, n := range envSettings.Node.Nodes {
				switch filepath.Base(n.Key) {
				case "revision":
					revision = n.Value
				case "branch":
					branch = n.Value
				case "deploy":
					deploy = n.Value
				case "repo_path":
					repoPath = n.Value
				}
			}
			//  Get Hosts per Environment.
			hosts, err := client.Get("/projects/"+name+"/environments/"+envName+"/hosts", false, false)
			if err != nil {
				return c, err
			}
			allHosts := []Host{}
			for _, h := range hosts.Node.Nodes {
				host := Host{URI: filepath.Base(h.Key)}
				allHosts = append(allHosts, host)
			}
			env := Environment{Name: envName, Deploy: deploy, RepoPath: repoPath, Branch: branch, Revision: revision}
			env.Hosts = allHosts
			allEnvironments = append(allEnvironments, env)
		}
		proj.Environments = allEnvironments
		allProjects = append(allProjects, proj)
	}
	piv := new(PivotalConfiguration)
	piv.Project = pivotalProject
	piv.Token = token
	sort.Sort(ByName(allProjects))
	c.Projects = allProjects
	c.DeployUser = deployUser
	c.Notify = notify
	c.Pivotal = piv

	return c, err
}
<|endoftext|>


<commit_before>/********************************
*** Web server API for Go     ***
*** Code is under MIT license ***
*** Code by CodingFerret      ***
*** github.com/squiidz        ***
*********************************/

package fur

import (
	"net/http"
)

type Route struct {
	Path    string
	handler http.Handler
	Method  string
}

func NewRoute(url string, h http.Handler, m string) *Route {
	return &Route{url, h, m}
}

func (r *Route) Get() {
	r.Method = "GET"
}

func (r *Route) Post() {
	r.Method = "POST"
}

func (r *Route) Put() {
	r.Method = "PUT"
}

func (r *Route) Delete() {
	r.Method = "DELETE"
}

func (r *Route) Head() {
	r.Method = "HEAD"
}

func (r *Route) Patch() {
	r.Method = "PATCH"
}

func (r *Route) Options() {
	r.Method = "OPTIONS"
}

func (r Route) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

	if r.Method != "" {
		if req.Method == r.Method {
			r.handler.ServeHTTP(rw, req)
		} else {
			rw.WriteHeader(http.StatusBadRequest)
		}
	} else {
		r.handler.ServeHTTP(rw, req)
	}

}
<commit_msg>Change NotFound() in route.ServeHTTP()<commit_after>/********************************
*** Web server API for Go     ***
*** Code is under MIT license ***
*** Code by CodingFerret      ***
*** github.com/squiidz        ***
*********************************/

package fur

import (
	"net/http"
)

type Route struct {
	Path    string
	handler http.Handler
	Method  string
}

func NewRoute(url string, h http.Handler, m string) *Route {
	return &Route{url, h, m}
}

func (r *Route) Get() {
	r.Method = "GET"
}

func (r *Route) Post() {
	r.Method = "POST"
}

func (r *Route) Put() {
	r.Method = "PUT"
}

func (r *Route) Delete() {
	r.Method = "DELETE"
}

func (r *Route) Head() {
	r.Method = "HEAD"
}

func (r *Route) Patch() {
	r.Method = "PATCH"
}

func (r *Route) Options() {
	r.Method = "OPTIONS"
}

func (r Route) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

	if r.Method != "" {
		if req.Method == r.Method {
			r.handler.ServeHTTP(rw, req)
		} else {
			http.NotFound(rw, req)
		}
	} else {
		r.handler.ServeHTTP(rw, req)
	}

}
<|endoftext|>


<commit_before>package reference

import (
	"regexp"

	// "opencontainers/go-digest" requires us to load the algorithms that we
	// want to use into the binary (it calls .Available).
	_ "crypto/sha256"

	distreference "github.com/docker/distribution/reference"
	"github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
)

const (
	// XDefaultTag defines the default tag used when performing images related actions and no tag or digest is specified
	XDefaultTag = "latest"
	// XDefaultHostname is the default built-in hostname
	XDefaultHostname = "docker.io"
	// XLegacyDefaultHostname is automatically converted to DefaultHostname
	XLegacyDefaultHostname = "index.docker.io"
	// XDefaultRepoPrefix is the prefix used for default repositories in default host
	XDefaultRepoPrefix = "library/"
)

// XCanonical reference is an object with a fully unique
// name including a name with hostname and digest
type XCanonical interface {
	distreference.Named
	XDigest() digest.Digest
}

// XParseNamed parses s and returns a syntactically valid reference implementing
// the Named interface. The reference must have a name, otherwise an error is
// returned.
// If an error was encountered it is returned, along with a nil Reference.
func XParseNamed(s string) (distreference.Named, error) {
	named, err := distreference.ParseNormalizedNamed(s)
	if err != nil {
		return nil, errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", s)
	}
	r, err := XWithName(named.Name())
	if err != nil {
		return nil, err
	}
	if canonical, isCanonical := named.(distreference.Canonical); isCanonical {
		r, err := distreference.WithDigest(r, canonical.Digest())
		if err != nil {
			return nil, err
		}
		return &canonicalRef{namedRef{r}}, nil
	}
	if tagged, isTagged := named.(distreference.NamedTagged); isTagged {
		return distreference.WithTag(r, tagged.Tag())
	}
	return r, nil
}

// XWithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
// FIXME: returns *namedRef to expose the distreference.Named implementation. Should revert to distreference.Named.
func XWithName(name string) (*namedRef, error) {
	r, err := distreference.ParseNormalizedNamed(name)
	if err != nil {
		return nil, err
	}
	return &namedRef{r}, nil
}

type namedRef struct {
	distreference.Named // FIXME: must implement private distreference.NamedRepository
}
type canonicalRef struct {
	namedRef
}

// TEMPORARY: distreference.WithDigest and distreference.WithTag can work with any distreference.Named,
// but if so, they break the values of distreference.Domain() and distreference.Path(),
// and hence also distreference.FamiliarName()/distreference.FamiliarString().  To preserve this,
// we need to implement a PRIVATE distreference.namedRepository.
// Similarly, we need to implement a PRIVATE distreference.normalizedNamed so that distreference.Familiar*()
// knows how to compute the minimal form.
// Right now that happens by these REALLY UGLY methods; eventually we will eliminate namedRef entirely in favor of
// distreference.Named, and distreference can keep its implementation games to itself.
type drPRIVATEInterfaces interface {
	distreference.Named
	Domain() string
	Path() string
	Familiar() distreference.Named
}

func (r *namedRef) Domain() string {
	return r.Named.(drPRIVATEInterfaces).Domain()
}
func (r *namedRef) Path() string {
	return r.Named.(drPRIVATEInterfaces).Path()
}
func (r *namedRef) Familiar() distreference.Named {
	return r.Named.(drPRIVATEInterfaces).Familiar()
}

func (r *canonicalRef) XDigest() digest.Digest {
	return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest())
}

// XWithDefaultTag adds a default tag to a reference if it only has a repo name.
func XWithDefaultTag(ref distreference.Named) distreference.Named {
	if XIsNameOnly(ref) {
		ref, _ = distreference.WithTag(ref, XDefaultTag)
	}
	return ref
}

// XIsNameOnly returns true if reference only contains a repo name.
func XIsNameOnly(ref distreference.Named) bool {
	if _, ok := ref.(distreference.NamedTagged); ok {
		return false
	}
	if _, ok := ref.(XCanonical); ok {
		return false
	}
	return true
}

// XParseIDOrReference parses string for an image ID or a reference. ID can be
// without a default prefix.
func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) {
	if err := validateID(idOrRef); err == nil {
		idOrRef = "sha256:" + idOrRef
	}
	if dgst, err := digest.Parse(idOrRef); err == nil {
		return dgst, nil, nil
	}
	ref, err := XParseNamed(idOrRef)
	return "", ref, err
}

var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)

func validateID(id string) error {
	if ok := validHex.MatchString(id); !ok {
		return errors.Errorf("image ID %q is invalid", id)
	}
	return nil
}
<commit_msg>Duplication: Have both a namedRef and Canonical in canonicalRef<commit_after>package reference

import (
	"regexp"

	// "opencontainers/go-digest" requires us to load the algorithms that we
	// want to use into the binary (it calls .Available).
	_ "crypto/sha256"

	distreference "github.com/docker/distribution/reference"
	"github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
)

const (
	// XDefaultTag defines the default tag used when performing images related actions and no tag or digest is specified
	XDefaultTag = "latest"
	// XDefaultHostname is the default built-in hostname
	XDefaultHostname = "docker.io"
	// XLegacyDefaultHostname is automatically converted to DefaultHostname
	XLegacyDefaultHostname = "index.docker.io"
	// XDefaultRepoPrefix is the prefix used for default repositories in default host
	XDefaultRepoPrefix = "library/"
)

// XCanonical reference is an object with a fully unique
// name including a name with hostname and digest
type XCanonical interface {
	distreference.Canonical
	XDigest() digest.Digest
}

// XParseNamed parses s and returns a syntactically valid reference implementing
// the Named interface. The reference must have a name, otherwise an error is
// returned.
// If an error was encountered it is returned, along with a nil Reference.
func XParseNamed(s string) (distreference.Named, error) {
	named, err := distreference.ParseNormalizedNamed(s)
	if err != nil {
		return nil, errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", s)
	}
	r, err := XWithName(named.Name())
	if err != nil {
		return nil, err
	}
	if canonical, isCanonical := named.(distreference.Canonical); isCanonical {
		r, err := distreference.WithDigest(r, canonical.Digest())
		if err != nil {
			return nil, err
		}
		return &canonicalRef{Canonical: r, namedRef: namedRef{r}}, nil
	}
	if tagged, isTagged := named.(distreference.NamedTagged); isTagged {
		return distreference.WithTag(r, tagged.Tag())
	}
	return r, nil
}

// XWithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
// FIXME: returns *namedRef to expose the distreference.Named implementation. Should revert to distreference.Named.
func XWithName(name string) (*namedRef, error) {
	r, err := distreference.ParseNormalizedNamed(name)
	if err != nil {
		return nil, err
	}
	return &namedRef{r}, nil
}

type namedRef struct {
	distreference.Named // FIXME: must implement private distreference.NamedRepository
}
type canonicalRef struct {
	distreference.Canonical
	namedRef
}

// TEMPORARY: distreference.WithDigest and distreference.WithTag can work with any distreference.Named,
// but if so, they break the values of distreference.Domain() and distreference.Path(),
// and hence also distreference.FamiliarName()/distreference.FamiliarString().  To preserve this,
// we need to implement a PRIVATE distreference.namedRepository.
// Similarly, we need to implement a PRIVATE distreference.normalizedNamed so that distreference.Familiar*()
// knows how to compute the minimal form.
// Right now that happens by these REALLY UGLY methods; eventually we will eliminate namedRef entirely in favor of
// distreference.Named, and distreference can keep its implementation games to itself.
type drPRIVATEInterfaces interface {
	distreference.Named
	Domain() string
	Path() string
	Familiar() distreference.Named
}

func (r *namedRef) Domain() string {
	return r.Named.(drPRIVATEInterfaces).Domain()
}
func (r *namedRef) Path() string {
	return r.Named.(drPRIVATEInterfaces).Path()
}
func (r *namedRef) Familiar() distreference.Named {
	return r.Named.(drPRIVATEInterfaces).Familiar()
}

func (r *canonicalRef) XDigest() digest.Digest {
	return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest())
}

// XWithDefaultTag adds a default tag to a reference if it only has a repo name.
func XWithDefaultTag(ref distreference.Named) distreference.Named {
	if XIsNameOnly(ref) {
		ref, _ = distreference.WithTag(ref, XDefaultTag)
	}
	return ref
}

// XIsNameOnly returns true if reference only contains a repo name.
func XIsNameOnly(ref distreference.Named) bool {
	if _, ok := ref.(distreference.NamedTagged); ok {
		return false
	}
	if _, ok := ref.(XCanonical); ok {
		return false
	}
	return true
}

// XParseIDOrReference parses string for an image ID or a reference. ID can be
// without a default prefix.
func XParseIDOrReference(idOrRef string) (digest.Digest, distreference.Named, error) {
	if err := validateID(idOrRef); err == nil {
		idOrRef = "sha256:" + idOrRef
	}
	if dgst, err := digest.Parse(idOrRef); err == nil {
		return dgst, nil, nil
	}
	ref, err := XParseNamed(idOrRef)
	return "", ref, err
}

var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)

func validateID(id string) error {
	if ok := validHex.MatchString(id); !ok {
		return errors.Errorf("image ID %q is invalid", id)
	}
	return nil
}
<|endoftext|>


<commit_before>package rrd

/*
#include <stdlib.h>
#include <rrd.h>
#include "rrdfunc.h"
#cgo LDFLAGS: -lrrd_th
*/
import "C"
import (
	"math"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"
	"unsafe"
)

func makeArgs(args []string) []*C.char {
	ret := make([]*C.char, len(args))
	for i, s := range args {
		ret[i] = C.CString(s)
	}
	return ret
}

func freeCString(s *C.char) {
	C.free(unsafe.Pointer(s))
}

func freeArgs(cArgs []*C.char) {
	for _, s := range cArgs {
		freeCString(s)
	}
}

func makeError(e *C.char) error {
	var null *C.char
	if e == null {
		return nil
	}
	defer freeCString(e)
	return Error(C.GoString(e))
}

func (c *Creator) create() error {
	filename := C.CString(c.filename)
	defer freeCString(filename)
	args := makeArgs(c.args)
	defer freeArgs(args)

	e := C.rrdCreate(
		filename,
		C.ulong(c.step),
		C.time_t(c.start.Unix()),
		C.int(len(args)),
		&args[0],
	)
	return makeError(e)
}

func (u *Updater) update(args []unsafe.Pointer) error {
	e := C.rrdUpdate(
		(*C.char)(u.filename.p()),
		(*C.char)(u.template.p()),
		C.int(len(args)),
		(**C.char)(unsafe.Pointer(&args[0])),
	)
	return makeError(e)
}

var (
	graphv           = C.CString("graphv")
	oStart           = C.CString("-s")
	oEnd             = C.CString("-e")
	oTitle           = C.CString("-t")
	oVlabel          = C.CString("-v")
	oWidth           = C.CString("-w")
	oHeight          = C.CString("-h")
	oUpperLimit      = C.CString("-u")
	oLowerLimit      = C.CString("-l")
	oRigid           = C.CString("-r")
	oAltAutoscale    = C.CString("-A")
	oAltAutoscaleMin = C.CString("-J")
	oAltAutoscaleMax = C.CString("-M")
	oNoGridFit       = C.CString("-N")

	oLogarithmic   = C.CString("-o")
	oUnitsExponent = C.CString("-X")
	oUnitsLength   = C.CString("-L")

	oRightAxis      = C.CString("--right-axis")
	oRightAxisLabel = C.CString("--right-axis-label")

	oNoLegend = C.CString("-g")

	oLazy = C.CString("-z")

	oColor = C.CString("-c")

	oSlopeMode   = C.CString("-E")
	oImageFormat = C.CString("-a")
	oInterlaced  = C.CString("-i")

	oBase      = C.CString("-b")
	oWatermark = C.CString("-W")
)

func ftoa(f float64) string {
	return strconv.FormatFloat(f, 'e', 10, 64)
}

func ftoc(f float64) *C.char {
	return C.CString(ftoa(f))
}

func i64toa(i int64) string {
	return strconv.FormatInt(i, 10)
}

func i64toc(i int64) *C.char {
	return C.CString(i64toa(i))
}

func u64toa(u uint64) string {
	return strconv.FormatUint(u, 10)
}

func u64toc(u uint64) *C.char {
	return C.CString(u64toa(u))
}
func itoa(i int) string {
	return i64toa(int64(i))
}

func itoc(i int) *C.char {
	return i64toc(int64(i))
}

func utoa(u uint) string {
	return u64toa(uint64(u))
}

func utoc(u uint) *C.char {
	return u64toc(uint64(u))
}

func (g *Grapher) makeArgs(filename string, start, end time.Time) []*C.char {
	args := []*C.char{
		graphv, C.CString(filename),
		oStart, i64toc(start.Unix()),
		oEnd, i64toc(end.Unix()),
		oTitle, C.CString(g.title),
		oVlabel, C.CString(g.vlabel),
	}
	if g.width != 0 {
		args = append(args, oWidth, utoc(g.width))
	}
	if g.height != 0 {
		args = append(args, oHeight, utoc(g.height))
	}
	if g.upperLimit != -math.MaxFloat64 {
		args = append(args, oUpperLimit, ftoc(g.upperLimit))
	}
	if g.lowerLimit != math.MaxFloat64 {
		args = append(args, oLowerLimit, ftoc(g.lowerLimit))
	}
	if g.rigid {
		args = append(args, oRigid)
	}
	if g.altAutoscale {
		args = append(args, oAltAutoscale)
	}
	if g.altAutoscaleMax {
		args = append(args, oAltAutoscaleMax)
	}
	if g.altAutoscaleMin {
		args = append(args, oAltAutoscaleMin)
	}
	if g.noGridFit {
		args = append(args, oNoGridFit)
	}
	if g.logarithmic {
		args = append(args, oLogarithmic)
	}
	if g.unitsExponent != minInt {
		args = append(
			args,
			oUnitsExponent, itoc(g.unitsExponent),
		)
	}
	if g.unitsLength != 0 {
		args = append(
			args,
			oUnitsLength, utoc(g.unitsLength),
		)
	}
	if g.rightAxisScale != 0 {
		args = append(
			args,
			oRightAxis,
			C.CString(ftoa(g.rightAxisScale)+":"+ftoa(g.rightAxisShift)),
		)
	}
	if g.rightAxisLabel != "" {
		args = append(
			args,
			oRightAxisLabel, C.CString(g.rightAxisLabel),
		)
	}
	if g.noLegend {
		args = append(args, oNoLegend)
	}
	if g.lazy {
		args = append(args, oLazy)
	}
	if g.color != "" {
		args = append(args, oColor, C.CString(g.color))
	}
	if g.slopeMode {
		args = append(args, oSlopeMode)
	}
	if g.imageFormat != "" {
		args = append(args, oImageFormat, C.CString(g.imageFormat))
	}
	if g.interlaced {
		args = append(args, oInterlaced)
	}
	if g.base != 0 {
		args = append(args, oInterlaced, utoc(g.base))
	}
	if g.watermark != "" {
		args = append(args, oWatermark, C.CString(g.watermark))
	}
	return append(args, makeArgs(g.args)...)
}

func parseInfoKey(ik string) (kname, kkey string, kid int) {
	kid = -1
	o := strings.IndexRune(ik, '[')
	if o == -1 {
		kname = ik
		return
	}
	c := strings.IndexRune(ik[o+1:], ']')
	if c == -1 {
		kname = ik
		return
	}
	c += o + 1
	kname = ik[:o] + ik[c+1:]
	kkey = ik[o+1 : c]
	if id, err := strconv.Atoi(kkey); err == nil && id >= 0 {
		kid = id
	}
	return
}

func updateInfoValue(i *C.struct_rrd_info_t, v interface{}) interface{} {
	switch i._type {
	case C.RD_I_VAL:
		return float64(*(*C.rrd_value_t)(unsafe.Pointer(&i.value[0])))
	case C.RD_I_CNT:
		return uint(*(*C.ulong)(unsafe.Pointer(&i.value[0])))
	case C.RD_I_STR:
		return C.GoString(*(**C.char)(unsafe.Pointer(&i.value[0])))
	case C.RD_I_INT:
		return int(*(*C.int)(unsafe.Pointer(&i.value[0])))
	case C.RD_I_BLO:
		blob := *(*C.rrd_blob_t)(unsafe.Pointer(&i.value[0]))
		b := C.GoBytes(unsafe.Pointer(blob.ptr), C.int(blob.size))
		if v == nil {
			return b
		}
		return append(v.([]byte), b...)
	}

	return nil
}

func parseRRDInfo(i *C.rrd_info_t) map[string]interface{} {
	defer C.rrd_info_free(i)

	r := make(map[string]interface{})
	for w := (*C.struct_rrd_info_t)(i); w != nil; w = w.next {
		kname, kkey, kid := parseInfoKey(C.GoString(w.key))
		v, ok := r[kname]
		switch {
		case kid != -1:
			var a []interface{}
			if ok {
				a = v.([]interface{})
			}
			if len(a) < kid+1 {
				oldA := a
				a = make([]interface{}, kid+1)
				copy(a, oldA)
			}
			a[kid] = updateInfoValue(w, a[kid])
			v = a
		case kkey != "":
			var m map[string]interface{}
			if ok {
				m = v.(map[string]interface{})
			} else {
				m = make(map[string]interface{})
			}
			old, _ := m[kkey]
			m[kkey] = updateInfoValue(w, old)
			v = m
		default:
			v = updateInfoValue(w, v)
		}
		r[kname] = v
	}
	return r
}

func parseGraphInfo(i *C.rrd_info_t) (gi GraphInfo, img []byte) {
	inf := parseRRDInfo(i)
	if v, ok := inf["image_info"]; ok {
		gi.Print = append(gi.Print, v.(string))
	}
	for k, v := range inf {
		if k == "print" {
			for _, line := range v.([]interface{}) {
				gi.Print = append(gi.Print, line.(string))
			}
		}
	}
	if v, ok := inf["image_width"]; ok {
		gi.Width = v.(uint)
	}
	if v, ok := inf["image_height"]; ok {
		gi.Height = v.(uint)
	}
	if v, ok := inf["value_min"]; ok {
		gi.Ymin = v.(float64)
	}
	if v, ok := inf["value_max"]; ok {
		gi.Ymax = v.(float64)
	}
	if v, ok := inf["image"]; ok {
		img = v.([]byte)
	}
	return
}

var graphMutex sync.Mutex

func (g *Grapher) graph(filename string, start, end time.Time) (GraphInfo, []byte, error) {
	var i *C.rrd_info_t
	args := g.makeArgs(filename, start, end)

	graphMutex.Lock() // rrd_graph_v isn't thread safe
	defer graphMutex.Unlock()

	err := makeError(C.rrdGraph(
		&i,
		C.int(len(args)),
		&args[0],
	))

	if err != nil {
		return GraphInfo{}, nil, err
	}
	gi, img := parseGraphInfo(i)

	return gi, img, nil
}

// Info returns information about RRD file.
func Info(filename string) (map[string]interface{}, error) {
	fn := C.CString(filename)
	defer freeCString(fn)
	var i *C.rrd_info_t
	err := makeError(C.rrdInfo(&i, fn))
	if err != nil {
		return nil, err
	}
	return parseRRDInfo(i), nil
}

// Fetch retrieves data from RRD file.
func Fetch(filename, cf string, start, end time.Time, step time.Duration) (FetchResult, error) {
	fn := C.CString(filename)
	defer freeCString(fn)
	cCf := C.CString(cf)
	defer freeCString(cCf)
	cStart := C.time_t(start.Unix())
	cEnd := C.time_t(end.Unix())
	cStep := C.ulong(step.Seconds())
	var (
		ret      C.int
		cDsCnt   C.ulong
		cDsNames **C.char
		cData    *C.double
	)
	err := makeError(C.rrdFetch(&ret, fn, cCf, &cStart, &cEnd, &cStep, &cDsCnt, &cDsNames, &cData))
	if err != nil {
		return FetchResult{filename, cf, start, end, step, nil, 0, nil}, err
	}

	start = time.Unix(int64(cStart), 0)
	end = time.Unix(int64(cEnd), 0)
	step = time.Duration(cStep) * time.Second
	dsCnt := int(cDsCnt)

	dsNames := make([]string, dsCnt)
	for i := 0; i < dsCnt; i++ {
		dsName := C.arrayGetCString(cDsNames, C.int(i))
		dsNames[i] = C.GoString(dsName)
		C.free(unsafe.Pointer(dsName))
	}
	C.free(unsafe.Pointer(cDsNames))

	rowCnt := (int(cEnd)-int(cStart))/int(cStep) + 1
	valuesLen := dsCnt * rowCnt
	values := make([]float64, valuesLen)
	sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&values)))
	sliceHeader.Cap = valuesLen
	sliceHeader.Len = valuesLen
	sliceHeader.Data = uintptr(unsafe.Pointer(cData))
	return FetchResult{filename, cf, start, end, step, dsNames, rowCnt, values}, nil
}

// FreeValues free values memory allocated by C.
func (r *FetchResult) FreeValues() {
	sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&r.values)))
	C.free(unsafe.Pointer(sliceHeader.Data))
}
<commit_msg>fix rrd's  LDFLAGS and CFLAGS path<commit_after>package rrd

/*
#include <stdlib.h>
#include <rrd.h>
#include "rrdfunc.h"
#cgo LDFLAGS: -lrrd_th -L/usr/local/lib
#cgo CFLAGS: -I/usr/local/include
*/
import "C"
import (
	"math"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"
	"unsafe"
)

func makeArgs(args []string) []*C.char {
	ret := make([]*C.char, len(args))
	for i, s := range args {
		ret[i] = C.CString(s)
	}
	return ret
}

func freeCString(s *C.char) {
	C.free(unsafe.Pointer(s))
}

func freeArgs(cArgs []*C.char) {
	for _, s := range cArgs {
		freeCString(s)
	}
}

func makeError(e *C.char) error {
	var null *C.char
	if e == null {
		return nil
	}
	defer freeCString(e)
	return Error(C.GoString(e))
}

func (c *Creator) create() error {
	filename := C.CString(c.filename)
	defer freeCString(filename)
	args := makeArgs(c.args)
	defer freeArgs(args)

	e := C.rrdCreate(
		filename,
		C.ulong(c.step),
		C.time_t(c.start.Unix()),
		C.int(len(args)),
		&args[0],
	)
	return makeError(e)
}

func (u *Updater) update(args []unsafe.Pointer) error {
	e := C.rrdUpdate(
		(*C.char)(u.filename.p()),
		(*C.char)(u.template.p()),
		C.int(len(args)),
		(**C.char)(unsafe.Pointer(&args[0])),
	)
	return makeError(e)
}

var (
	graphv           = C.CString("graphv")
	oStart           = C.CString("-s")
	oEnd             = C.CString("-e")
	oTitle           = C.CString("-t")
	oVlabel          = C.CString("-v")
	oWidth           = C.CString("-w")
	oHeight          = C.CString("-h")
	oUpperLimit      = C.CString("-u")
	oLowerLimit      = C.CString("-l")
	oRigid           = C.CString("-r")
	oAltAutoscale    = C.CString("-A")
	oAltAutoscaleMin = C.CString("-J")
	oAltAutoscaleMax = C.CString("-M")
	oNoGridFit       = C.CString("-N")

	oLogarithmic   = C.CString("-o")
	oUnitsExponent = C.CString("-X")
	oUnitsLength   = C.CString("-L")

	oRightAxis      = C.CString("--right-axis")
	oRightAxisLabel = C.CString("--right-axis-label")

	oNoLegend = C.CString("-g")

	oLazy = C.CString("-z")

	oColor = C.CString("-c")

	oSlopeMode   = C.CString("-E")
	oImageFormat = C.CString("-a")
	oInterlaced  = C.CString("-i")

	oBase      = C.CString("-b")
	oWatermark = C.CString("-W")
)

func ftoa(f float64) string {
	return strconv.FormatFloat(f, 'e', 10, 64)
}

func ftoc(f float64) *C.char {
	return C.CString(ftoa(f))
}

func i64toa(i int64) string {
	return strconv.FormatInt(i, 10)
}

func i64toc(i int64) *C.char {
	return C.CString(i64toa(i))
}

func u64toa(u uint64) string {
	return strconv.FormatUint(u, 10)
}

func u64toc(u uint64) *C.char {
	return C.CString(u64toa(u))
}
func itoa(i int) string {
	return i64toa(int64(i))
}

func itoc(i int) *C.char {
	return i64toc(int64(i))
}

func utoa(u uint) string {
	return u64toa(uint64(u))
}

func utoc(u uint) *C.char {
	return u64toc(uint64(u))
}

func (g *Grapher) makeArgs(filename string, start, end time.Time) []*C.char {
	args := []*C.char{
		graphv, C.CString(filename),
		oStart, i64toc(start.Unix()),
		oEnd, i64toc(end.Unix()),
		oTitle, C.CString(g.title),
		oVlabel, C.CString(g.vlabel),
	}
	if g.width != 0 {
		args = append(args, oWidth, utoc(g.width))
	}
	if g.height != 0 {
		args = append(args, oHeight, utoc(g.height))
	}
	if g.upperLimit != -math.MaxFloat64 {
		args = append(args, oUpperLimit, ftoc(g.upperLimit))
	}
	if g.lowerLimit != math.MaxFloat64 {
		args = append(args, oLowerLimit, ftoc(g.lowerLimit))
	}
	if g.rigid {
		args = append(args, oRigid)
	}
	if g.altAutoscale {
		args = append(args, oAltAutoscale)
	}
	if g.altAutoscaleMax {
		args = append(args, oAltAutoscaleMax)
	}
	if g.altAutoscaleMin {
		args = append(args, oAltAutoscaleMin)
	}
	if g.noGridFit {
		args = append(args, oNoGridFit)
	}
	if g.logarithmic {
		args = append(args, oLogarithmic)
	}
	if g.unitsExponent != minInt {
		args = append(
			args,
			oUnitsExponent, itoc(g.unitsExponent),
		)
	}
	if g.unitsLength != 0 {
		args = append(
			args,
			oUnitsLength, utoc(g.unitsLength),
		)
	}
	if g.rightAxisScale != 0 {
		args = append(
			args,
			oRightAxis,
			C.CString(ftoa(g.rightAxisScale)+":"+ftoa(g.rightAxisShift)),
		)
	}
	if g.rightAxisLabel != "" {
		args = append(
			args,
			oRightAxisLabel, C.CString(g.rightAxisLabel),
		)
	}
	if g.noLegend {
		args = append(args, oNoLegend)
	}
	if g.lazy {
		args = append(args, oLazy)
	}
	if g.color != "" {
		args = append(args, oColor, C.CString(g.color))
	}
	if g.slopeMode {
		args = append(args, oSlopeMode)
	}
	if g.imageFormat != "" {
		args = append(args, oImageFormat, C.CString(g.imageFormat))
	}
	if g.interlaced {
		args = append(args, oInterlaced)
	}
	if g.base != 0 {
		args = append(args, oInterlaced, utoc(g.base))
	}
	if g.watermark != "" {
		args = append(args, oWatermark, C.CString(g.watermark))
	}
	return append(args, makeArgs(g.args)...)
}

func parseInfoKey(ik string) (kname, kkey string, kid int) {
	kid = -1
	o := strings.IndexRune(ik, '[')
	if o == -1 {
		kname = ik
		return
	}
	c := strings.IndexRune(ik[o+1:], ']')
	if c == -1 {
		kname = ik
		return
	}
	c += o + 1
	kname = ik[:o] + ik[c+1:]
	kkey = ik[o+1 : c]
	if id, err := strconv.Atoi(kkey); err == nil && id >= 0 {
		kid = id
	}
	return
}

func updateInfoValue(i *C.struct_rrd_info_t, v interface{}) interface{} {
	switch i._type {
	case C.RD_I_VAL:
		return float64(*(*C.rrd_value_t)(unsafe.Pointer(&i.value[0])))
	case C.RD_I_CNT:
		return uint(*(*C.ulong)(unsafe.Pointer(&i.value[0])))
	case C.RD_I_STR:
		return C.GoString(*(**C.char)(unsafe.Pointer(&i.value[0])))
	case C.RD_I_INT:
		return int(*(*C.int)(unsafe.Pointer(&i.value[0])))
	case C.RD_I_BLO:
		blob := *(*C.rrd_blob_t)(unsafe.Pointer(&i.value[0]))
		b := C.GoBytes(unsafe.Pointer(blob.ptr), C.int(blob.size))
		if v == nil {
			return b
		}
		return append(v.([]byte), b...)
	}

	return nil
}

func parseRRDInfo(i *C.rrd_info_t) map[string]interface{} {
	defer C.rrd_info_free(i)

	r := make(map[string]interface{})
	for w := (*C.struct_rrd_info_t)(i); w != nil; w = w.next {
		kname, kkey, kid := parseInfoKey(C.GoString(w.key))
		v, ok := r[kname]
		switch {
		case kid != -1:
			var a []interface{}
			if ok {
				a = v.([]interface{})
			}
			if len(a) < kid+1 {
				oldA := a
				a = make([]interface{}, kid+1)
				copy(a, oldA)
			}
			a[kid] = updateInfoValue(w, a[kid])
			v = a
		case kkey != "":
			var m map[string]interface{}
			if ok {
				m = v.(map[string]interface{})
			} else {
				m = make(map[string]interface{})
			}
			old, _ := m[kkey]
			m[kkey] = updateInfoValue(w, old)
			v = m
		default:
			v = updateInfoValue(w, v)
		}
		r[kname] = v
	}
	return r
}

func parseGraphInfo(i *C.rrd_info_t) (gi GraphInfo, img []byte) {
	inf := parseRRDInfo(i)
	if v, ok := inf["image_info"]; ok {
		gi.Print = append(gi.Print, v.(string))
	}
	for k, v := range inf {
		if k == "print" {
			for _, line := range v.([]interface{}) {
				gi.Print = append(gi.Print, line.(string))
			}
		}
	}
	if v, ok := inf["image_width"]; ok {
		gi.Width = v.(uint)
	}
	if v, ok := inf["image_height"]; ok {
		gi.Height = v.(uint)
	}
	if v, ok := inf["value_min"]; ok {
		gi.Ymin = v.(float64)
	}
	if v, ok := inf["value_max"]; ok {
		gi.Ymax = v.(float64)
	}
	if v, ok := inf["image"]; ok {
		img = v.([]byte)
	}
	return
}

var graphMutex sync.Mutex

func (g *Grapher) graph(filename string, start, end time.Time) (GraphInfo, []byte, error) {
	var i *C.rrd_info_t
	args := g.makeArgs(filename, start, end)

	graphMutex.Lock() // rrd_graph_v isn't thread safe
	defer graphMutex.Unlock()

	err := makeError(C.rrdGraph(
		&i,
		C.int(len(args)),
		&args[0],
	))

	if err != nil {
		return GraphInfo{}, nil, err
	}
	gi, img := parseGraphInfo(i)

	return gi, img, nil
}

// Info returns information about RRD file.
func Info(filename string) (map[string]interface{}, error) {
	fn := C.CString(filename)
	defer freeCString(fn)
	var i *C.rrd_info_t
	err := makeError(C.rrdInfo(&i, fn))
	if err != nil {
		return nil, err
	}
	return parseRRDInfo(i), nil
}

// Fetch retrieves data from RRD file.
func Fetch(filename, cf string, start, end time.Time, step time.Duration) (FetchResult, error) {
	fn := C.CString(filename)
	defer freeCString(fn)
	cCf := C.CString(cf)
	defer freeCString(cCf)
	cStart := C.time_t(start.Unix())
	cEnd := C.time_t(end.Unix())
	cStep := C.ulong(step.Seconds())
	var (
		ret      C.int
		cDsCnt   C.ulong
		cDsNames **C.char
		cData    *C.double
	)
	err := makeError(C.rrdFetch(&ret, fn, cCf, &cStart, &cEnd, &cStep, &cDsCnt, &cDsNames, &cData))
	if err != nil {
		return FetchResult{filename, cf, start, end, step, nil, 0, nil}, err
	}

	start = time.Unix(int64(cStart), 0)
	end = time.Unix(int64(cEnd), 0)
	step = time.Duration(cStep) * time.Second
	dsCnt := int(cDsCnt)

	dsNames := make([]string, dsCnt)
	for i := 0; i < dsCnt; i++ {
		dsName := C.arrayGetCString(cDsNames, C.int(i))
		dsNames[i] = C.GoString(dsName)
		C.free(unsafe.Pointer(dsName))
	}
	C.free(unsafe.Pointer(cDsNames))

	rowCnt := (int(cEnd)-int(cStart))/int(cStep) + 1
	valuesLen := dsCnt * rowCnt
	values := make([]float64, valuesLen)
	sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&values)))
	sliceHeader.Cap = valuesLen
	sliceHeader.Len = valuesLen
	sliceHeader.Data = uintptr(unsafe.Pointer(cData))
	return FetchResult{filename, cf, start, end, step, dsNames, rowCnt, values}, nil
}

// FreeValues free values memory allocated by C.
func (r *FetchResult) FreeValues() {
	sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&r.values)))
	C.free(unsafe.Pointer(sliceHeader.Data))
}
<|endoftext|>