<commit_before>// Copyright 2015 Square Inc.
//
// 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 registry

import (
	"fmt"
	"math"
	"sort"
	"strings"

	"github.com/square/metrics/api"
	"github.com/square/metrics/function"
	"github.com/square/metrics/function/aggregate"
	"github.com/square/metrics/function/filter"
	"github.com/square/metrics/function/join"
	"github.com/square/metrics/function/transform"
)

func init() {
	// Arithmetic operators
	MustRegister(NewOperator("+", func(x float64, y float64) float64 { return x + y }))
	MustRegister(NewOperator("-", func(x float64, y float64) float64 { return x - y }))
	MustRegister(NewOperator("*", func(x float64, y float64) float64 { return x * y }))
	MustRegister(NewOperator("/", func(x float64, y float64) float64 { return x / y }))
	// Aggregates
	MustRegister(NewAggregate("aggregate.max", aggregate.Max))
	MustRegister(NewAggregate("aggregate.min", aggregate.Min))
	MustRegister(NewAggregate("aggregate.mean", aggregate.Mean))
	MustRegister(NewAggregate("aggregate.sum", aggregate.Sum))
	MustRegister(NewAggregate("aggregate.total", aggregate.Total))
	MustRegister(NewAggregate("aggregate.count", aggregate.Count))
	// Transformations
	MustRegister(NewTransform("transform.derivative", 0, transform.Derivative))
	MustRegister(NewTransform("transform.integral", 0, transform.Integral))
	MustRegister(NewTransform("transform.rate", 0, transform.Rate))
	MustRegister(NewTransform("transform.cumulative", 0, transform.Cumulative))
	MustRegister(NewTransform("transform.default", 1, transform.Default))
	MustRegister(NewTransform("transform.abs", 0, transform.MapMaker(math.Abs)))
	MustRegister(NewTransform("transform.log", 0, transform.MapMaker(math.Log10)))
	MustRegister(NewTransform("transform.nan_keep_last", 0, transform.NaNKeepLast))
	MustRegister(NewTransform("transform.bound", 2, transform.Bound))
	MustRegister(NewTransform("transform.lower_bound", 1, transform.LowerBound))
	MustRegister(NewTransform("transform.upper_bound", 1, transform.UpperBound))
	// Filter
	MustRegister(NewFilter("filter.highest_mean", aggregate.Mean, false))
	MustRegister(NewFilter("filter.lowest_mean", aggregate.Mean, true))
	MustRegister(NewFilter("filter.highest_max", aggregate.Max, false))
	MustRegister(NewFilter("filter.lowest_max", aggregate.Max, true))
	MustRegister(NewFilter("filter.highest_min", aggregate.Min, false))
	MustRegister(NewFilter("filter.lowest_min", aggregate.Min, true))
	// Filter Recent
	MustRegister(NewFilterRecent("filter.recent_highest_mean", aggregate.Mean, false))
	MustRegister(NewFilterRecent("filter.recent_lowest_mean", aggregate.Mean, true))
	MustRegister(NewFilterRecent("filter.recent_highest_max", aggregate.Max, false))
	MustRegister(NewFilterRecent("filter.recent_lowest_max", aggregate.Max, true))
	MustRegister(NewFilterRecent("filter.recent_highest_min", aggregate.Min, false))
	MustRegister(NewFilterRecent("filter.recent_lowest_min", aggregate.Min, true))
	// Weird ones
	MustRegister(transform.Timeshift)
	MustRegister(transform.Alias)
	MustRegister(transform.MovingAverage)
}

// StandardRegistry of a functions available in MQE.
type StandardRegistry struct {
	mapping map[string]function.MetricFunction
}

var defaultRegistry = StandardRegistry{mapping: make(map[string]function.MetricFunction)}

func Default() StandardRegistry {
	return defaultRegistry
}

// GetFunction returns a function associated with the given name, if it exists.
func (r StandardRegistry) GetFunction(name string) (function.MetricFunction, bool) {
	fun, ok := r.mapping[name]
	return fun, ok
}

func (r StandardRegistry) All() []string {
	result := make([]string, len(r.mapping))
	counter := 0
	for key := range r.mapping {
		result[counter] = key
		counter++
	}
	sort.Strings(result)
	return result
}

// Register a new function into the registry.
func (r StandardRegistry) Register(fun function.MetricFunction) error {
	_, ok := r.mapping[fun.Name]
	if ok {
		return fmt.Errorf("function %s has already been registered", fun.Name)
	}
	if fun.Compute == nil {
		return fmt.Errorf("function %s has no Compute() field.", fun.Name)
	}
	if fun.Name == "" {
		return fmt.Errorf("empty function name.")
	}
	r.mapping[fun.Name] = fun
	return nil
}

// MustRegister adds a new metric function to the global function registry.
func MustRegister(fun function.MetricFunction) {
	err := defaultRegistry.Register(fun)
	if err != nil {
		panic(fmt.Sprintf("function %s in failed to register", fun.Name))
	}
}

// Constructor Functions

// NewFilter creates a new instance of a filtering function.
func NewFilter(name string, summary func([]float64) float64, ascending bool) function.MetricFunction {
	return function.MetricFunction{
		Name:         name,
		MinArguments: 2,
		MaxArguments: 2,
		Compute: func(context function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
			value, err := arguments[0].Evaluate(context)
			if err != nil {
				return nil, err
			}
			// The value must be a SeriesList.
			list, err := value.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			countValue, err := arguments[1].Evaluate(context)
			if err != nil {
				return nil, err
			}
			countFloat, err := countValue.ToScalar()
			if err != nil {
				return nil, err
			}
			// Round to the nearest integer.
			count := int(countFloat + 0.5)
			if count < 0 {
				return nil, fmt.Errorf("expected positive count but got %d", count)
			}
			result := filter.FilterBy(list, count, summary, ascending)
			result.Name = fmt.Sprintf("%s(%s, %d)", name, value.GetName(), count)
			return function.SeriesListValue(result), nil
		},
	}
}

// NewFilterRecent creates a new instance of a recent-filtering function.
func NewFilterRecent(name string, summary func([]float64) float64, ascending bool) function.MetricFunction {
	return function.MetricFunction{
		Name:         name,
		MinArguments: 3,
		MaxArguments: 3,
		Compute: func(context function.EvaluationContext, arguments []function.Expression, groups []string) (function.Value, error) {
			value, err := arguments[0].Evaluate(context)
			if err != nil {
				return nil, err
			}
			// The value must be a SeriesList.
			list, err := value.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			countValue, err := arguments[1].Evaluate(context)
			if err != nil {
				return nil, err
			}
			countFloat, err := countValue.ToScalar()
			if err != nil {
				return nil, err
			}
			// Round to the nearest integer.
			count := int(countFloat + 0.5)
			if count < 0 {
				return nil, fmt.Errorf("expected positive count but got %d", count)
			}
			durationValue, err := arguments[2].Evaluate(context)
			if err != nil {
				return nil, err
			}
			duration, err := durationValue.ToDuration()
			if err != nil {
				return nil, err
			}
			result := filter.FilterRecentBy(list, count, summary, ascending, duration)
			result.Name = fmt.Sprintf("%s(%s, %d)", name, value.GetName(), count)
			return function.SeriesListValue(result), nil
		},
	}
}

// NewAggregate takes a named aggregating function `[float64] => float64` and makes it into a MetricFunction.
func NewAggregate(name string, aggregator func([]float64) float64) function.MetricFunction {
	return function.MetricFunction{
		Name:          name,
		MinArguments:  1,
		MaxArguments:  1,
		AllowsGroupBy: true,
		Compute: func(context function.EvaluationContext, args []function.Expression, groups function.Groups) (function.Value, error) {
			argument := args[0]
			value, err := argument.Evaluate(context)
			if err != nil {
				return nil, err
			}
			seriesList, err := value.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			result := aggregate.AggregateBy(seriesList, aggregator, groups.List, groups.Collapses)
			groupNames := make([]string, len(groups.List))
			for i, group := range groups.List {
				groupNames[i] += group
			}
			if len(groups.List) == 0 {
				result.Name = fmt.Sprintf("%s(%s)", name, value.GetName())
			} else {
				verbName := "group"
				if groups.Collapses {
					verbName = "collapse"
				}
				result.Name = fmt.Sprintf("%s(%s %s by %s)", name, value.GetName(), verbName, strings.Join(groupNames, ", "))
			}
			return function.SeriesListValue(result), nil
		},
	}
}

// NewTransform takes a named transforming function `[float64], [value] => [float64]` and makes it into a MetricFunction.
func NewTransform(name string, parameterCount int, transformer func([]float64, []function.Value, float64) ([]float64, error)) function.MetricFunction {
	return function.MetricFunction{
		Name:         name,
		MinArguments: parameterCount + 1,
		MaxArguments: parameterCount + 1,
		Compute: func(context function.EvaluationContext, args []function.Expression, groups function.Groups) (function.Value, error) {
			listValue, err := args[0].Evaluate(context)
			if err != nil {
				return nil, err
			}
			list, err := listValue.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			parameters := make([]function.Value, parameterCount)
			for i := range parameters {
				parameters[i], err = args[i+1].Evaluate(context)
				if err != nil {
					return nil, err
				}
			}
			result, err := transform.ApplyTransform(list, transformer, parameters)
			if err != nil {
				return nil, err
			}
			parameterNames := make([]string, len(parameters))
			for i, param := range parameters {
				parameterNames[i] = param.GetName()
			}
			if len(parameters) != 0 {
				result.Name = fmt.Sprintf("%s(%s, %s)", name, listValue.GetName(), strings.Join(parameterNames, ", "))
			} else {
				result.Name = fmt.Sprintf("%s(%s)", name, listValue.GetName())
			}
			return function.SeriesListValue(result), nil
		},
	}
}

// NewOperator creates a new binary operator function.
// the binary operators display a natural join semantic.
func NewOperator(op string, operator func(float64, float64) float64) function.MetricFunction {
	return function.MetricFunction{
		Name:         op,
		MinArguments: 2,
		MaxArguments: 2,
		Compute: func(context function.EvaluationContext, args []function.Expression, groups function.Groups) (function.Value, error) {
			leftChannel := make(chan function.Value, 1)
			rightChannel := make(chan function.Value, 1)
			errs := make(chan error, 2)
			go func() {
				leftValue, err := args[0].Evaluate(context)
				leftChannel <- leftValue
				errs <- err
			}()
			go func() {
				rightValue, err := args[1].Evaluate(context)
				rightChannel <- rightValue
				errs <- err
			}()
			err := <-errs
			if err != nil {
				return nil, err
			}
			err = <-errs
			if err != nil {
				return nil, err
			}
			leftValue := <-leftChannel
			rightValue := <-rightChannel

			leftList, err := leftValue.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			rightList, err := rightValue.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}

			joined := join.Join([]api.SeriesList{leftList, rightList})

			result := make([]api.Timeseries, len(joined.Rows))

			for i, row := range joined.Rows {
				left := row.Row[0]
				right := row.Row[1]
				array := make([]float64, len(left.Values))
				for j := 0; j < len(left.Values); j++ {
					array[j] = operator(left.Values[j], right.Values[j])
				}
				result[i] = api.Timeseries{array, row.TagSet}
			}

			return function.SeriesListValue(api.SeriesList{
				Series:    result,
				Timerange: context.Timerange,
				Name:      fmt.Sprintf("(%s %s %s)", leftValue.GetName(), op, rightValue.GetName()),
			}), nil
		},
	}
}
<commit_msg>update to use collapse-group<commit_after>// Copyright 2015 Square Inc.
//
// 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 registry

import (
	"fmt"
	"math"
	"sort"
	"strings"

	"github.com/square/metrics/api"
	"github.com/square/metrics/function"
	"github.com/square/metrics/function/aggregate"
	"github.com/square/metrics/function/filter"
	"github.com/square/metrics/function/join"
	"github.com/square/metrics/function/transform"
)

func init() {
	// Arithmetic operators
	MustRegister(NewOperator("+", func(x float64, y float64) float64 { return x + y }))
	MustRegister(NewOperator("-", func(x float64, y float64) float64 { return x - y }))
	MustRegister(NewOperator("*", func(x float64, y float64) float64 { return x * y }))
	MustRegister(NewOperator("/", func(x float64, y float64) float64 { return x / y }))
	// Aggregates
	MustRegister(NewAggregate("aggregate.max", aggregate.Max))
	MustRegister(NewAggregate("aggregate.min", aggregate.Min))
	MustRegister(NewAggregate("aggregate.mean", aggregate.Mean))
	MustRegister(NewAggregate("aggregate.sum", aggregate.Sum))
	MustRegister(NewAggregate("aggregate.total", aggregate.Total))
	MustRegister(NewAggregate("aggregate.count", aggregate.Count))
	// Transformations
	MustRegister(NewTransform("transform.derivative", 0, transform.Derivative))
	MustRegister(NewTransform("transform.integral", 0, transform.Integral))
	MustRegister(NewTransform("transform.rate", 0, transform.Rate))
	MustRegister(NewTransform("transform.cumulative", 0, transform.Cumulative))
	MustRegister(NewTransform("transform.default", 1, transform.Default))
	MustRegister(NewTransform("transform.abs", 0, transform.MapMaker(math.Abs)))
	MustRegister(NewTransform("transform.log", 0, transform.MapMaker(math.Log10)))
	MustRegister(NewTransform("transform.nan_keep_last", 0, transform.NaNKeepLast))
	MustRegister(NewTransform("transform.bound", 2, transform.Bound))
	MustRegister(NewTransform("transform.lower_bound", 1, transform.LowerBound))
	MustRegister(NewTransform("transform.upper_bound", 1, transform.UpperBound))
	// Filter
	MustRegister(NewFilter("filter.highest_mean", aggregate.Mean, false))
	MustRegister(NewFilter("filter.lowest_mean", aggregate.Mean, true))
	MustRegister(NewFilter("filter.highest_max", aggregate.Max, false))
	MustRegister(NewFilter("filter.lowest_max", aggregate.Max, true))
	MustRegister(NewFilter("filter.highest_min", aggregate.Min, false))
	MustRegister(NewFilter("filter.lowest_min", aggregate.Min, true))
	// Filter Recent
	MustRegister(NewFilterRecent("filter.recent_highest_mean", aggregate.Mean, false))
	MustRegister(NewFilterRecent("filter.recent_lowest_mean", aggregate.Mean, true))
	MustRegister(NewFilterRecent("filter.recent_highest_max", aggregate.Max, false))
	MustRegister(NewFilterRecent("filter.recent_lowest_max", aggregate.Max, true))
	MustRegister(NewFilterRecent("filter.recent_highest_min", aggregate.Min, false))
	MustRegister(NewFilterRecent("filter.recent_lowest_min", aggregate.Min, true))
	// Weird ones
	MustRegister(transform.Timeshift)
	MustRegister(transform.Alias)
	MustRegister(transform.MovingAverage)
}

// StandardRegistry of a functions available in MQE.
type StandardRegistry struct {
	mapping map[string]function.MetricFunction
}

var defaultRegistry = StandardRegistry{mapping: make(map[string]function.MetricFunction)}

func Default() StandardRegistry {
	return defaultRegistry
}

// GetFunction returns a function associated with the given name, if it exists.
func (r StandardRegistry) GetFunction(name string) (function.MetricFunction, bool) {
	fun, ok := r.mapping[name]
	return fun, ok
}

func (r StandardRegistry) All() []string {
	result := make([]string, len(r.mapping))
	counter := 0
	for key := range r.mapping {
		result[counter] = key
		counter++
	}
	sort.Strings(result)
	return result
}

// Register a new function into the registry.
func (r StandardRegistry) Register(fun function.MetricFunction) error {
	_, ok := r.mapping[fun.Name]
	if ok {
		return fmt.Errorf("function %s has already been registered", fun.Name)
	}
	if fun.Compute == nil {
		return fmt.Errorf("function %s has no Compute() field.", fun.Name)
	}
	if fun.Name == "" {
		return fmt.Errorf("empty function name.")
	}
	r.mapping[fun.Name] = fun
	return nil
}

// MustRegister adds a new metric function to the global function registry.
func MustRegister(fun function.MetricFunction) {
	err := defaultRegistry.Register(fun)
	if err != nil {
		panic(fmt.Sprintf("function %s in failed to register", fun.Name))
	}
}

// Constructor Functions

// NewFilter creates a new instance of a filtering function.
func NewFilter(name string, summary func([]float64) float64, ascending bool) function.MetricFunction {
	return function.MetricFunction{
		Name:         name,
		MinArguments: 2,
		MaxArguments: 2,
		Compute: func(context function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
			value, err := arguments[0].Evaluate(context)
			if err != nil {
				return nil, err
			}
			// The value must be a SeriesList.
			list, err := value.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			countValue, err := arguments[1].Evaluate(context)
			if err != nil {
				return nil, err
			}
			countFloat, err := countValue.ToScalar()
			if err != nil {
				return nil, err
			}
			// Round to the nearest integer.
			count := int(countFloat + 0.5)
			if count < 0 {
				return nil, fmt.Errorf("expected positive count but got %d", count)
			}
			result := filter.FilterBy(list, count, summary, ascending)
			result.Name = fmt.Sprintf("%s(%s, %d)", name, value.GetName(), count)
			return function.SeriesListValue(result), nil
		},
	}
}

// NewFilterRecent creates a new instance of a recent-filtering function.
func NewFilterRecent(name string, summary func([]float64) float64, ascending bool) function.MetricFunction {
	return function.MetricFunction{
		Name:         name,
		MinArguments: 3,
		MaxArguments: 3,
		Compute: func(context function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
			value, err := arguments[0].Evaluate(context)
			if err != nil {
				return nil, err
			}
			// The value must be a SeriesList.
			list, err := value.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			countValue, err := arguments[1].Evaluate(context)
			if err != nil {
				return nil, err
			}
			countFloat, err := countValue.ToScalar()
			if err != nil {
				return nil, err
			}
			// Round to the nearest integer.
			count := int(countFloat + 0.5)
			if count < 0 {
				return nil, fmt.Errorf("expected positive count but got %d", count)
			}
			durationValue, err := arguments[2].Evaluate(context)
			if err != nil {
				return nil, err
			}
			duration, err := durationValue.ToDuration()
			if err != nil {
				return nil, err
			}
			result := filter.FilterRecentBy(list, count, summary, ascending, duration)
			result.Name = fmt.Sprintf("%s(%s, %d)", name, value.GetName(), count)
			return function.SeriesListValue(result), nil
		},
	}
}

// NewAggregate takes a named aggregating function `[float64] => float64` and makes it into a MetricFunction.
func NewAggregate(name string, aggregator func([]float64) float64) function.MetricFunction {
	return function.MetricFunction{
		Name:          name,
		MinArguments:  1,
		MaxArguments:  1,
		AllowsGroupBy: true,
		Compute: func(context function.EvaluationContext, args []function.Expression, groups function.Groups) (function.Value, error) {
			argument := args[0]
			value, err := argument.Evaluate(context)
			if err != nil {
				return nil, err
			}
			seriesList, err := value.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			result := aggregate.AggregateBy(seriesList, aggregator, groups.List, groups.Collapses)
			groupNames := make([]string, len(groups.List))
			for i, group := range groups.List {
				groupNames[i] += group
			}
			if len(groups.List) == 0 {
				result.Name = fmt.Sprintf("%s(%s)", name, value.GetName())
			} else {
				verbName := "group"
				if groups.Collapses {
					verbName = "collapse"
				}
				result.Name = fmt.Sprintf("%s(%s %s by %s)", name, value.GetName(), verbName, strings.Join(groupNames, ", "))
			}
			return function.SeriesListValue(result), nil
		},
	}
}

// NewTransform takes a named transforming function `[float64], [value] => [float64]` and makes it into a MetricFunction.
func NewTransform(name string, parameterCount int, transformer func([]float64, []function.Value, float64) ([]float64, error)) function.MetricFunction {
	return function.MetricFunction{
		Name:         name,
		MinArguments: parameterCount + 1,
		MaxArguments: parameterCount + 1,
		Compute: func(context function.EvaluationContext, args []function.Expression, groups function.Groups) (function.Value, error) {
			listValue, err := args[0].Evaluate(context)
			if err != nil {
				return nil, err
			}
			list, err := listValue.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			parameters := make([]function.Value, parameterCount)
			for i := range parameters {
				parameters[i], err = args[i+1].Evaluate(context)
				if err != nil {
					return nil, err
				}
			}
			result, err := transform.ApplyTransform(list, transformer, parameters)
			if err != nil {
				return nil, err
			}
			parameterNames := make([]string, len(parameters))
			for i, param := range parameters {
				parameterNames[i] = param.GetName()
			}
			if len(parameters) != 0 {
				result.Name = fmt.Sprintf("%s(%s, %s)", name, listValue.GetName(), strings.Join(parameterNames, ", "))
			} else {
				result.Name = fmt.Sprintf("%s(%s)", name, listValue.GetName())
			}
			return function.SeriesListValue(result), nil
		},
	}
}

// NewOperator creates a new binary operator function.
// the binary operators display a natural join semantic.
func NewOperator(op string, operator func(float64, float64) float64) function.MetricFunction {
	return function.MetricFunction{
		Name:         op,
		MinArguments: 2,
		MaxArguments: 2,
		Compute: func(context function.EvaluationContext, args []function.Expression, groups function.Groups) (function.Value, error) {
			leftChannel := make(chan function.Value, 1)
			rightChannel := make(chan function.Value, 1)
			errs := make(chan error, 2)
			go func() {
				leftValue, err := args[0].Evaluate(context)
				leftChannel <- leftValue
				errs <- err
			}()
			go func() {
				rightValue, err := args[1].Evaluate(context)
				rightChannel <- rightValue
				errs <- err
			}()
			err := <-errs
			if err != nil {
				return nil, err
			}
			err = <-errs
			if err != nil {
				return nil, err
			}
			leftValue := <-leftChannel
			rightValue := <-rightChannel

			leftList, err := leftValue.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}
			rightList, err := rightValue.ToSeriesList(context.Timerange)
			if err != nil {
				return nil, err
			}

			joined := join.Join([]api.SeriesList{leftList, rightList})

			result := make([]api.Timeseries, len(joined.Rows))

			for i, row := range joined.Rows {
				left := row.Row[0]
				right := row.Row[1]
				array := make([]float64, len(left.Values))
				for j := 0; j < len(left.Values); j++ {
					array[j] = operator(left.Values[j], right.Values[j])
				}
				result[i] = api.Timeseries{array, row.TagSet}
			}

			return function.SeriesListValue(api.SeriesList{
				Series:    result,
				Timerange: context.Timerange,
				Name:      fmt.Sprintf("(%s %s %s)", leftValue.GetName(), op, rightValue.GetName()),
			}), nil
		},
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Square Inc.
//
// 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 transform

import (
	"fmt"
	"math"
	"strconv"

	"github.com/square/metrics/api"
	"github.com/square/metrics/function"
)

var Timeshift = function.MetricFunction{
	Name:         "transform.timeshift",
	MinArguments: 2,
	MaxArguments: 2,
	Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
		value, err := arguments[1].Evaluate(context)
		if err != nil {
			return nil, err
		}
		duration, err := value.ToDuration()
		if err != nil {
			return nil, err
		}
		newContext := context
		newContext.Timerange = newContext.Timerange.Shift(duration)

		result, err := arguments[0].Evaluate(newContext)
		if err != nil {
			return nil, err
		}

		if seriesValue, ok := result.(api.SeriesList); ok {
			seriesValue.Timerange = context.Timerange
			seriesValue.Query = fmt.Sprintf("transform.timeshift(%s,%s)", result.GetName(), value.GetName())
			seriesValue.Name = seriesValue.Query
			return seriesValue, nil
		}
		return result, nil
	},
}

var MovingAverage = function.MetricFunction{
	Name:         "transform.moving_average",
	MinArguments: 2,
	MaxArguments: 2,
	Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
		// Applying a similar trick as did TimeshiftFunction. It fetches data prior to the start of the timerange.

		sizeValue, err := arguments[1].Evaluate(context)
		if err != nil {
			return nil, err
		}
		size, err := sizeValue.ToDuration()
		if err != nil {
			return nil, err
		}
		limit := int(float64(size)/float64(context.Timerange.Resolution()) + 0.5) // Limit is the number of items to include in the average
		if limit < 1 {
			// At least one value must be included at all times
			limit = 1
		}

		newContext := context.Copy()
		timerange := context.Timerange
		newContext.Timerange, err = api.NewSnappedTimerange(timerange.Start()-int64(limit-1)*timerange.ResolutionMillis(), timerange.End(), timerange.ResolutionMillis())
		if err != nil {
			return nil, err
		}
		// The new context has a timerange which is extended beyond the query's.
		listValue, err := arguments[0].Evaluate(&newContext)
		if err != nil {
			return nil, err
		}

		// This value must be a SeriesList.
		list, err := listValue.ToSeriesList(newContext.Timerange)
		if err != nil {
			return nil, err
		}

		// The timerange must be reverted.
		list.Timerange = context.Timerange
		context.CopyNotesFrom(&newContext)
		newContext.Invalidate() //Prevent this from leaking or getting used.

		// Update each series in the list.
		for index, series := range list.Series {
			// The series will be given a (shorter) replaced list of values.
			results := make([]float64, context.Timerange.Slots())
			count := 0
			sum := 0.0
			for i := range series.Values {
				// Add the new element, if it isn't NaN.
				if !math.IsNaN(series.Values[i]) {
					sum += series.Values[i]
					count++
				}
				// Remove the oldest element, if it isn't NaN, and it's in range.
				// (e.g., if limit = 1, then this removes the previous element from the sum).
				if i >= limit && !math.IsNaN(series.Values[i-limit]) {
					sum -= series.Values[i-limit]
					count--
				}
				// Numerical error could (possibly) cause count == 0 but sum != 0.
				if i-limit+1 >= 0 {
					if count == 0 {
						results[i-limit+1] = math.NaN()
					} else {
						results[i-limit+1] = sum / float64(count)
					}
				}
			}
			list.Series[index].Values = results
		}
		list.Query = fmt.Sprintf("transform.moving_average(%s, %s)", listValue.GetName(), sizeValue.GetName())
		list.Name = list.Query
		return list, nil
	},
}

var Alias = function.MetricFunction{
	Name:         "transform.alias",
	MinArguments: 2,
	MaxArguments: 2,
	Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
		value, err := arguments[0].Evaluate(context)
		if err != nil {
			return nil, err
		}
		list, err := value.ToSeriesList(context.Timerange)
		if err != nil {
			return nil, err
		}
		nameValue, err := arguments[1].Evaluate(context)
		if err != nil {
			return nil, err
		}
		name, err := nameValue.ToString()
		if err != nil {
			return nil, err
		}
		list.Name = name
		list.Query = fmt.Sprintf("transform.alias(%s, %s)", value.GetName(), strconv.Quote(name))
		return list, nil
	},
}

// Derivative is special because it needs to get one extra data point to the left
// This transform estimates the "change per second" between the two samples (scaled consecutive difference)
var Derivative = newDerivativeBasedTransform("derivative", derivative)

func derivative(ctx *function.EvaluationContext, series api.Timeseries, parameters []function.Value, scale float64) ([]float64, error) {
	values := series.Values
	result := make([]float64, len(values)-1)
	for i := range values {
		if i == 0 {
			continue
		}
		// Scaled difference
		result[i-1] = (values[i] - values[i-1]) / scale
	}
	return result, nil
}

// Rate is special because it needs to get one extra data point to the left.
// This transform functions mostly like Derivative but bounds the result to be positive.
// Specifically this function is designed for strictly increasing counters that
// only decrease when reset to zero. That is, thie function returns consecutive
// differences which are at least 0, or math.Max of the newly reported value and 0
var Rate = newDerivativeBasedTransform("rate", rate)

func rate(ctx *function.EvaluationContext, series api.Timeseries, parameters []function.Value, scale float64) ([]float64, error) {
	values := series.Values
	result := make([]float64, len(values)-1)
	for i := range values {
		if i == 0 {
			continue
		}
		// Scaled difference
		result[i-1] = (values[i] - values[i-1]) / scale
		if result[i-1] < 0 {
			result[i-1] = 0
		}
		if i+1 < len(values) && values[i-1] > values[i] && values[i] <= values[i+1] {
			// Downsampling may cause a drop from 1000 to 0 to look like [1000, 500, 0] instead of [1000, 1001, 0].
			// So we check the next, in addition to the previous.
			ctx.AddNote(fmt.Sprintf("Rate(%v): The underlying counter reset between %f, %f\n", series.TagSet, values[i-1], values[i]))
			// values[i] is our best approximatation of the delta between i-1 and i
			// Why? This should only be used on counters, so if v[i] - v[i-1] < 0 then
			// the counter has reset, and we know *at least* v[i] increments have happened
			result[i-1] = math.Max(values[i], 0) / scale
		}
	}
	return result, nil
}

// newDerivativeBasedTransform returns a function.MetricFunction that performs
// a delta between two data points. The transform parameter is a function of type
// transform is expected to return an array of values whose length is 1 less
// than the given series
func newDerivativeBasedTransform(name string, transformer transform) function.MetricFunction {
	return function.MetricFunction{
		Name:         "transform." + name,
		MinArguments: 1,
		MaxArguments: 1,
		Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
			var err error
			// Calcuate the new timerange to include one extra point to the left
			newContext := context.Copy()
			timerange := context.Timerange
			newContext.Timerange, err = api.NewSnappedTimerange(timerange.Start()-timerange.ResolutionMillis(), timerange.End(), timerange.ResolutionMillis())
			if err != nil {
				return nil, err
			}

			// The new context has a timerange which is extended beyond the query's.
			listValue, err := arguments[0].Evaluate(&newContext)
			if err != nil {
				return nil, err
			}

			// This value must be a SeriesList.
			list, err := listValue.ToSeriesList(newContext.Timerange)
			if err != nil {
				return nil, err
			}

			// Reset the timerange
			list.Timerange = context.Timerange
			context.CopyNotesFrom(&newContext)
			newContext.Invalidate() // Prevent leaking this around.

			//Apply the original context to the transform even though the list
			//will include one additional data point.
			result, err := ApplyTransform(context, list, transformer, []function.Value{})
			if err != nil {
				return nil, err
			}

			// Validate our series are the correct length
			for i := range result.Series {
				if len(result.Series[i].Values) != len(list.Series[i].Values)-1 {
					panic(fmt.Sprintf("Expected transform to return %d values, received %d", len(list.Series[i].Values)-1, len(result.Series[i].Values)))
				}
			}

			result.Query = fmt.Sprintf("transform.%s(%s)", name, listValue.GetName())
			result.Name = result.Query
			return result, nil
		},
	}
}
<commit_msg>define exponential moving average function<commit_after>// Copyright 2015 Square Inc.
//
// 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 transform

import (
	"fmt"
	"math"
	"strconv"

	"github.com/square/metrics/api"
	"github.com/square/metrics/function"
)

var Timeshift = function.MetricFunction{
	Name:         "transform.timeshift",
	MinArguments: 2,
	MaxArguments: 2,
	Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
		value, err := arguments[1].Evaluate(context)
		if err != nil {
			return nil, err
		}
		duration, err := value.ToDuration()
		if err != nil {
			return nil, err
		}
		newContext := context
		newContext.Timerange = newContext.Timerange.Shift(duration)

		result, err := arguments[0].Evaluate(newContext)
		if err != nil {
			return nil, err
		}

		if seriesValue, ok := result.(api.SeriesList); ok {
			seriesValue.Timerange = context.Timerange
			seriesValue.Query = fmt.Sprintf("transform.timeshift(%s,%s)", result.GetName(), value.GetName())
			seriesValue.Name = seriesValue.Query
			return seriesValue, nil
		}
		return result, nil
	},
}

var MovingAverage = function.MetricFunction{
	Name:         "transform.moving_average",
	MinArguments: 2,
	MaxArguments: 2,
	Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
		// Applying a similar trick as did TimeshiftFunction. It fetches data prior to the start of the timerange.

		sizeValue, err := arguments[1].Evaluate(context)
		if err != nil {
			return nil, err
		}
		size, err := sizeValue.ToDuration()
		if err != nil {
			return nil, err
		}
		limit := int(float64(size)/float64(context.Timerange.Resolution()) + 0.5) // Limit is the number of items to include in the average
		if limit < 1 {
			// At least one value must be included at all times
			limit = 1
		}

		newContext := context.Copy()
		timerange := context.Timerange
		newContext.Timerange, err = api.NewSnappedTimerange(timerange.Start()-int64(limit-1)*timerange.ResolutionMillis(), timerange.End(), timerange.ResolutionMillis())
		if err != nil {
			return nil, err
		}
		// The new context has a timerange which is extended beyond the query's.
		listValue, err := arguments[0].Evaluate(&newContext)
		if err != nil {
			return nil, err
		}

		// This value must be a SeriesList.
		list, err := listValue.ToSeriesList(newContext.Timerange)
		if err != nil {
			return nil, err
		}

		// The timerange must be reverted.
		list.Timerange = context.Timerange
		context.CopyNotesFrom(&newContext)
		newContext.Invalidate() //Prevent this from leaking or getting used.

		// Update each series in the list.
		for index, series := range list.Series {
			// The series will be given a (shorter) replaced list of values.
			results := make([]float64, context.Timerange.Slots())
			count := 0
			sum := 0.0
			for i := range series.Values {
				// Add the new element, if it isn't NaN.
				if !math.IsNaN(series.Values[i]) {
					sum += series.Values[i]
					count++
				}
				// Remove the oldest element, if it isn't NaN, and it's in range.
				// (e.g., if limit = 1, then this removes the previous element from the sum).
				if i >= limit && !math.IsNaN(series.Values[i-limit]) {
					sum -= series.Values[i-limit]
					count--
				}
				// Numerical error could (possibly) cause count == 0 but sum != 0.
				if i-limit+1 >= 0 {
					if count == 0 {
						results[i-limit+1] = math.NaN()
					} else {
						results[i-limit+1] = sum / float64(count)
					}
				}
			}
			list.Series[index].Values = results
		}
		list.Query = fmt.Sprintf("transform.moving_average(%s, %s)", listValue.GetName(), sizeValue.GetName())
		list.Name = list.Query
		return list, nil
	},
}

var ExponentialMovingAverage = function.MetricFunction{
	Name:         "transform.exponential_moving_average",
	MinArguments: 2,
	MaxArguments: 2,
	Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
		// Applying a similar trick as did TimeshiftFunction. It fetches data prior to the start of the timerange.

		sizeValue, err := arguments[1].Evaluate(context)
		if err != nil {
			return nil, err
		}
		size, err := sizeValue.ToDuration()
		if err != nil {
			return nil, err
		}
		limit := int(float64(size)/float64(context.Timerange.Resolution()) + 0.5) // Limit is the number of items to include in the average
		if limit < 1 {
			// At least one value must be included at all times
			limit = 1
		}

		newContext := context.Copy()
		timerange := context.Timerange
		newContext.Timerange, err = api.NewSnappedTimerange(timerange.Start()-int64(limit-1)*timerange.ResolutionMillis(), timerange.End(), timerange.ResolutionMillis())
		if err != nil {
			return nil, err
		}
		// The new context has a timerange which is extended beyond the query's.
		listValue, err := arguments[0].Evaluate(&newContext)
		if err != nil {
			return nil, err
		}

		// This value must be a SeriesList.
		list, err := listValue.ToSeriesList(newContext.Timerange)
		if err != nil {
			return nil, err
		}

		// How many "ticks" are there in "size"?
		// size / resolution
		// alpha is a parameter such that
		// alpha^ticks = 1/2
		// so, alpha = exp(log(1/2) / ticks)
		alpha := math.Exp(math.Log(0.5) * float64(context.Timerange.Resolution()) / float64(size))

		// The timerange must be reverted.
		list.Timerange = context.Timerange
		context.CopyNotesFrom(&newContext)
		newContext.Invalidate() //Prevent this from leaking or getting used.

		// Update each series in the list.
		for index, series := range list.Series {
			// The series will be given a (shorter) replaced list of values.
			results := make([]float64, context.Timerange.Slots())
			weight := 0.0
			sum := 0.0
			for i := range series.Values {
				weight *= alpha
				sum *= alpha
				if !math.IsNaN(series.Values[i]) {
					weight += 1
					sum += series.Values[i]
				}
				results[i-limit+1] = sum / weight
			}
			list.Series[index].Values = results
		}
		list.Query = fmt.Sprintf("transform.exponential_moving_average(%s, %s)", listValue.GetName(), sizeValue.GetName())
		list.Name = list.Query
		return list, nil
	},
}

var Alias = function.MetricFunction{
	Name:         "transform.alias",
	MinArguments: 2,
	MaxArguments: 2,
	Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
		value, err := arguments[0].Evaluate(context)
		if err != nil {
			return nil, err
		}
		list, err := value.ToSeriesList(context.Timerange)
		if err != nil {
			return nil, err
		}
		nameValue, err := arguments[1].Evaluate(context)
		if err != nil {
			return nil, err
		}
		name, err := nameValue.ToString()
		if err != nil {
			return nil, err
		}
		list.Name = name
		list.Query = fmt.Sprintf("transform.alias(%s, %s)", value.GetName(), strconv.Quote(name))
		return list, nil
	},
}

// Derivative is special because it needs to get one extra data point to the left
// This transform estimates the "change per second" between the two samples (scaled consecutive difference)
var Derivative = newDerivativeBasedTransform("derivative", derivative)

func derivative(ctx *function.EvaluationContext, series api.Timeseries, parameters []function.Value, scale float64) ([]float64, error) {
	values := series.Values
	result := make([]float64, len(values)-1)
	for i := range values {
		if i == 0 {
			continue
		}
		// Scaled difference
		result[i-1] = (values[i] - values[i-1]) / scale
	}
	return result, nil
}

// Rate is special because it needs to get one extra data point to the left.
// This transform functions mostly like Derivative but bounds the result to be positive.
// Specifically this function is designed for strictly increasing counters that
// only decrease when reset to zero. That is, thie function returns consecutive
// differences which are at least 0, or math.Max of the newly reported value and 0
var Rate = newDerivativeBasedTransform("rate", rate)

func rate(ctx *function.EvaluationContext, series api.Timeseries, parameters []function.Value, scale float64) ([]float64, error) {
	values := series.Values
	result := make([]float64, len(values)-1)
	for i := range values {
		if i == 0 {
			continue
		}
		// Scaled difference
		result[i-1] = (values[i] - values[i-1]) / scale
		if result[i-1] < 0 {
			result[i-1] = 0
		}
		if i+1 < len(values) && values[i-1] > values[i] && values[i] <= values[i+1] {
			// Downsampling may cause a drop from 1000 to 0 to look like [1000, 500, 0] instead of [1000, 1001, 0].
			// So we check the next, in addition to the previous.
			ctx.AddNote(fmt.Sprintf("Rate(%v): The underlying counter reset between %f, %f\n", series.TagSet, values[i-1], values[i]))
			// values[i] is our best approximatation of the delta between i-1 and i
			// Why? This should only be used on counters, so if v[i] - v[i-1] < 0 then
			// the counter has reset, and we know *at least* v[i] increments have happened
			result[i-1] = math.Max(values[i], 0) / scale
		}
	}
	return result, nil
}

// newDerivativeBasedTransform returns a function.MetricFunction that performs
// a delta between two data points. The transform parameter is a function of type
// transform is expected to return an array of values whose length is 1 less
// than the given series
func newDerivativeBasedTransform(name string, transformer transform) function.MetricFunction {
	return function.MetricFunction{
		Name:         "transform." + name,
		MinArguments: 1,
		MaxArguments: 1,
		Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) {
			var err error
			// Calcuate the new timerange to include one extra point to the left
			newContext := context.Copy()
			timerange := context.Timerange
			newContext.Timerange, err = api.NewSnappedTimerange(timerange.Start()-timerange.ResolutionMillis(), timerange.End(), timerange.ResolutionMillis())
			if err != nil {
				return nil, err
			}

			// The new context has a timerange which is extended beyond the query's.
			listValue, err := arguments[0].Evaluate(&newContext)
			if err != nil {
				return nil, err
			}

			// This value must be a SeriesList.
			list, err := listValue.ToSeriesList(newContext.Timerange)
			if err != nil {
				return nil, err
			}

			// Reset the timerange
			list.Timerange = context.Timerange
			context.CopyNotesFrom(&newContext)
			newContext.Invalidate() // Prevent leaking this around.

			//Apply the original context to the transform even though the list
			//will include one additional data point.
			result, err := ApplyTransform(context, list, transformer, []function.Value{})
			if err != nil {
				return nil, err
			}

			// Validate our series are the correct length
			for i := range result.Series {
				if len(result.Series[i].Values) != len(list.Series[i].Values)-1 {
					panic(fmt.Sprintf("Expected transform to return %d values, received %d", len(list.Series[i].Values)-1, len(result.Series[i].Values)))
				}
			}

			result.Query = fmt.Sprintf("transform.%s(%s)", name, listValue.GetName())
			result.Name = result.Query
			return result, nil
		},
	}
}
<|endoftext|>


<commit_before>/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you 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 main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/karlseguin/rcache"
	"github.com/nickvanw/ircx"
	"github.com/sorcix/irc"
)

type PlaceInfo struct {
	PlaceName string  `json:"place name"`
	State     string  `json:"state"`
	StateAbbr string  `json:"state abbreviation"`
	Latitude  float64 `json:"latitude,string"`
	Longitude float64 `json:"longitude,string"`
}

type ZipInfo struct {
	PostCode    string      `json:"post code"`
	Country     string      `json:"country"`
	CountryAbbr string      `json:"country abbreviation"`
	Places      []PlaceInfo `json:"places"`
}

type Current struct {
	Time                 int64   `json:"time"`
	Summary              string  `json:"summary"`
	Icon                 string  `json:"icon"`
	NearestStormDistance float64 `json:"nearestStormDistance"`
	NearestStormBearing  float64 `json:"nearestStormBearing"`
	PrecipIntensity      float64 `json:"precipIntensity"`
	PrecipProbability    float64 `json:"precipProbability"`
	Temperature          float64 `json:"temperature"`
	ApparentTemperature  float64 `json:"apparentTemperature"`
	DewPoint             float64 `json:"dewPoint"`
	Humidity             float64 `json:"humidity"`
	WindSpeed            float64 `json:"windSpeed"`
	WindBearing          float64 `json:"windBearing"`
	Visibility           float64 `json:"visibility"`
	CloudCover           float64 `json:"cloudCover"`
	Pressure             float64 `json:"pressure"`
	Ozone                float64 `json:"ozone"`
}

type Minutely struct {
	Summary string `json:"summary"`
}

type Hourly struct {
	Summary string `json:"summary"`
}

type Daily struct {
	Summary string `json:"summary"`
}

type WeatherReport struct {
	Latitude  float64  `json:"latitude"`
	Longitude float64  `json:"longitude"`
	Timezone  string   `json:"timezone"`
	Offset    float64  `json:"offset"`
	Currently Current  `json:"currently"`
	Minutely  Minutely `json:"minutely"`
	Hourly    Hourly   `json:"hourly"`
	Daily     Daily    `json:"daily"`
}

var cache *rcache.Cache

func fetcher(key string) interface{} {
	var z ZipInfo

	log.Println("Looking up coordinates for zip:", key)
	resp, err := http.Get(fmt.Sprintf("http://api.zippopotam.us/us/%s", key))

	if err != nil {
		return nil
	}

	defer resp.Body.Close()
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&z)

	if err != nil {
		return nil
	}

	return &z
}

func init() {
	cache = rcache.New(fetcher, time.Hour*24*7)
}

func GetWeather(s ircx.Sender, message *irc.Message) {
	if len(message.Trailing) == 5 {
		if _, err := strconv.Atoi(message.Trailing); err == nil {
			p := message.Params
			if p[0] == config.General.Name {
				p = []string{message.Prefix.Name}
			}

			m := &irc.Message{
				Command: irc.PRIVMSG,
				Params:  p,
			}

			z := cache.Get(message.Trailing).(*ZipInfo)

			if z != nil && z.Places != nil {
				resp, err := http.Get(fmt.Sprint("https://api.forecast.io/forecast/", config.Forecast.Key, "/",
					z.Places[0].Latitude, ",", z.Places[0].Longitude, "?exclude=flags"))
				if err != nil {
					// handle error
					return
				}
				defer resp.Body.Close()

				dec := json.NewDecoder(resp.Body)

				var w WeatherReport
				err = dec.Decode(&w)

				l, _ := time.LoadLocation(w.Timezone)

				t := time.Unix(w.Currently.Time, 0).In(l)

				log.Println("Sending weather for", message.Trailing)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": ", z.Places[0].PlaceName, ", ", z.Places[0].StateAbbr,
					" (", z.Places[0].Latitude, ", ", z.Places[0].Longitude, ") ", t, " - ",
					w.Currently.Temperature, "F (feels like ", w.Currently.ApparentTemperature, "F) - ",
					w.Currently.Summary)
				s.Send(m)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": ",
					w.Currently.Humidity*100, "% Humidity - ",
					"Wind from ", w.Currently.WindBearing, "° at ", w.Currently.WindSpeed, "MPH - ",
					"Visibility ", w.Currently.Visibility, " Miles - ",
					"Cloud Cover ", w.Currently.CloudCover*100, "% - ",
					"Precipitation Probability ", w.Currently.PrecipProbability*100, "%")
				s.Send(m)

				m.Trailing = fmt.Sprint(message.Prefix.Name, ": ", w.Minutely.Summary, " ", w.Hourly.Summary, " ", w.Daily.Summary)
				s.Send(m)
			}
		}
	}
}
<commit_msg>Must check for nil *before* type assertion, even to pointer type<commit_after>/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you 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 main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/karlseguin/rcache"
	"github.com/nickvanw/ircx"
	"github.com/sorcix/irc"
)

type PlaceInfo struct {
	PlaceName string  `json:"place name"`
	State     string  `json:"state"`
	StateAbbr string  `json:"state abbreviation"`
	Latitude  float64 `json:"latitude,string"`
	Longitude float64 `json:"longitude,string"`
}

type ZipInfo struct {
	PostCode    string      `json:"post code"`
	Country     string      `json:"country"`
	CountryAbbr string      `json:"country abbreviation"`
	Places      []PlaceInfo `json:"places"`
}

type Current struct {
	Time                 int64   `json:"time"`
	Summary              string  `json:"summary"`
	Icon                 string  `json:"icon"`
	NearestStormDistance float64 `json:"nearestStormDistance"`
	NearestStormBearing  float64 `json:"nearestStormBearing"`
	PrecipIntensity      float64 `json:"precipIntensity"`
	PrecipProbability    float64 `json:"precipProbability"`
	Temperature          float64 `json:"temperature"`
	ApparentTemperature  float64 `json:"apparentTemperature"`
	DewPoint             float64 `json:"dewPoint"`
	Humidity             float64 `json:"humidity"`
	WindSpeed            float64 `json:"windSpeed"`
	WindBearing          float64 `json:"windBearing"`
	Visibility           float64 `json:"visibility"`
	CloudCover           float64 `json:"cloudCover"`
	Pressure             float64 `json:"pressure"`
	Ozone                float64 `json:"ozone"`
}

type Minutely struct {
	Summary string `json:"summary"`
}

type Hourly struct {
	Summary string `json:"summary"`
}

type Daily struct {
	Summary string `json:"summary"`
}

type WeatherReport struct {
	Latitude  float64  `json:"latitude"`
	Longitude float64  `json:"longitude"`
	Timezone  string   `json:"timezone"`
	Offset    float64  `json:"offset"`
	Currently Current  `json:"currently"`
	Minutely  Minutely `json:"minutely"`
	Hourly    Hourly   `json:"hourly"`
	Daily     Daily    `json:"daily"`
}

var cache *rcache.Cache

func fetcher(key string) interface{} {
	var z ZipInfo

	log.Println("Looking up coordinates for zip:", key)
	resp, err := http.Get(fmt.Sprintf("http://api.zippopotam.us/us/%s", key))

	if err != nil {
		log.Printf("Lookup failed for zip: %s (%s)\n", key, err)
		return nil
	}

	defer resp.Body.Close()
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&z)

	if err != nil {
		log.Printf("Unable to parse result for zip: %s (%s)\n", key, err)
		return nil
	}

	return &z
}

func init() {
	cache = rcache.New(fetcher, time.Hour*24*7)
}

func GetWeather(s ircx.Sender, message *irc.Message) {
	if len(message.Trailing) == 5 {
		if _, err := strconv.Atoi(message.Trailing); err == nil {
			p := message.Params
			if p[0] == config.General.Name {
				p = []string{message.Prefix.Name}
			}

			m := &irc.Message{
				Command: irc.PRIVMSG,
				Params:  p,
			}

			zl := cache.Get(message.Trailing)

			if zl != nil {
				z := zl.(*ZipInfo)
				if z.Places != nil {
					resp, err := http.Get(fmt.Sprint("https://api.forecast.io/forecast/", config.Forecast.Key, "/",
						z.Places[0].Latitude, ",", z.Places[0].Longitude, "?exclude=flags"))
					if err != nil {
						// handle error
						return
					}
					defer resp.Body.Close()

					dec := json.NewDecoder(resp.Body)

					var w WeatherReport
					err = dec.Decode(&w)

					l, _ := time.LoadLocation(w.Timezone)

					t := time.Unix(w.Currently.Time, 0).In(l)

					log.Println("Sending weather for", message.Trailing)

					m.Trailing = fmt.Sprint(message.Prefix.Name, ": ", z.Places[0].PlaceName, ", ", z.Places[0].StateAbbr,
						" (", z.Places[0].Latitude, ", ", z.Places[0].Longitude, ") ", t, " - ",
						w.Currently.Temperature, "F (feels like ", w.Currently.ApparentTemperature, "F) - ",
						w.Currently.Summary)
					s.Send(m)

					m.Trailing = fmt.Sprint(message.Prefix.Name, ": ",
						w.Currently.Humidity*100, "% Humidity - ",
						"Wind from ", w.Currently.WindBearing, "° at ", w.Currently.WindSpeed, "MPH - ",
						"Visibility ", w.Currently.Visibility, " Miles - ",
						"Cloud Cover ", w.Currently.CloudCover*100, "% - ",
						"Precipitation Probability ", w.Currently.PrecipProbability*100, "%")
					s.Send(m)

					m.Trailing = fmt.Sprint(message.Prefix.Name, ": ", w.Minutely.Summary, " ", w.Hourly.Summary, " ", w.Daily.Summary)
					s.Send(m)
				} else {
					log.Println("No data returned for zip:", message.Trailing)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>// Package ircclient provides the main interface for library users
// It manages a single connection to the server and the associated
// configuration and plugins.
package ircclient

import (
	"os"
	"strings"
)

type IRCClient struct {
	conn       *IRCConn
	conf       map[string]string
	plugins    *pluginStack
	disconnect chan bool
}

// Returns a new IRCClient connection with the given configuration options.
// It will not connect to the given server until Connect() has been called,
// so you can register plugins before connecting
func NewIRCClient(hostport, nick, rname, ident string, trigger string) *IRCClient {
	c := &IRCClient{nil, make(map[string]string), newPluginStack(), make(chan bool)}
	c.conf["nick"] = nick
	c.conf["hostport"] = hostport
	c.conf["rname"] = rname
	c.conf["ident"] = ident
	c.conf["trigger"] = trigger
	c.RegisterPlugin(&basicProtocol{})
	return c
}

// Registers a new plugin. Plugins can be registered at any time, even before
// the actual connection attempt. The plugin's Unregister() function will already
// be called when the connection is lost.
func (ic *IRCClient) RegisterPlugin(p Plugin) os.Error {
	if _, ok := ic.plugins.GetPlugin(p.String()); ok == true {
		return os.NewError("Plugin already exists")
	}
	p.Register(ic)
	ic.plugins.Push(p)
	return nil
}

// Connects to the server specified on object creation. If the chosen nickname is
// already in use, it will automatically be suffixed with an single underscore until
// an unused nickname is found. This function blocks until the connection attempt
// has been finished.
func (ic *IRCClient) Connect() os.Error {
	ic.conn = NewIRCConn()
	e := ic.conn.Connect(ic.conf["hostport"])
	if e != nil {
		return e
	}
	ic.conn.Output <- "NICK " + ic.conf["nick"]
	ic.conn.Output <- "USER " + ic.conf["ident"] + " * Q :" + ic.conf["rname"]
	nick := ic.conf["nick"]
	for {
		line, ok := <-ic.conn.Input
		if !ok {
			return <-ic.conn.Err
		}

		// Invoke plugin line handlers.
		// At this point, it makes no sense to
		// process "commands". If a plugin needs
		// interaction in this state, it should be
		// low-level.
		s := ParseServerLine(line)
		if s == nil {
			continue
		}
		for p := range ic.plugins.Iter() {
			go p.ProcessLine(s)
		}

		switch s.Command {
		case "433":
			// Nickname already in use
			nick = nick + "_"
			ic.conf["nick"] = nick
			ic.conn.Output <- "NICK " + nick
		case "001":
			// Successfully registered
			return nil
		}
	}
	return nil
}

func (ic *IRCClient) dispatchHandlers(in string) {
	var c *IRCCommand = nil

	s := ParseServerLine(in)
	if s == nil {
		return
	}
	if (s.Command == "PRIVMSG" || s.Command == "NOTICE") && (s.Target == ic.conf["nick"] || s.Args[0][0] == ic.conf["trigger"][0]) {
		c = ParseCommand(s)
		// Strip trigger, if necessary
		if c != nil && s.Target != ic.conf["nick"] && len(c.Command) != 0 {
			c.Command = c.Command[len(ic.conf["trigger"]):len(c.Command)]
		}
	}

	for p := range ic.plugins.Iter() {
		go p.ProcessLine(s)
		if c != nil {
			go p.ProcessCommand(c)
		}
	}
}

// Starts the actual command processing. This function will block until the connection
// has either been lost or Disconnect() has been called (by a plugin or by the library
// user).
func (ic *IRCClient) InputLoop() os.Error {
	for {
		in, ok := <-ic.conn.Input
		if !ok {
			return <-ic.conn.Err
		}
		ic.dispatchHandlers(in)
	}
	panic("This never happens")
}

// Disconnects from the server with the given quit message. All plugins wil be unregistered
// and pending messages in queue (e.g. because of floodprotection) will be flushed. This will
// also make InputLoop() return.
func (ic *IRCClient) Disconnect(quitmsg string) {
	ic.shutdown()
	ic.conn.Output <- "QUIT :" + quitmsg
	ic.conn.Quit()
}

// Gets one of the configuration options supplied to the NewIRCClient() method. Valid config
// options usually include:
//  - nick
//  - hostport (colon-seperated host and port to connect to)
//  - rname (the real name)
//  - ident
//  - trigger
func (ic *IRCClient) GetConfOpt(option string) string {
	return ic.conf[option]
}

// Sets a configuration option (see also GetConfOpt())
func (ic *IRCClient) SetConfOpt(option string) {
	ic.conf[option] = option
}

// Dumps a raw line to the server socket. This is usually called by plugins, but may also
// be used by the library user.
func (ic *IRCClient) SendLine(line string) {
	ic.conn.Output <- line
}

func (ic *IRCClient) shutdown() {
	for ic.plugins.Size() != 0 {
		p := ic.plugins.Pop()
		p.Unregister()
	}
}

// Gets the current nickname. Note: This is equivalent to a call to GetConfOpt("nick") and
// might be removed in the future. Better use GetConfOpt() for this purpose
func (ic *IRCClient) GetNick() string {
	return ic.conf["nick"]
}

// Returns a channel on which all plugins will be sent. Use it to iterate over all registered
// plugins.
func (ic *IRCClient) IterPlugins() <-chan Plugin {
	return ic.plugins.Iter()
}

// Get the pointer to a specific plugin that has been registered using RegisterPlugin()
// Name is the name the plugin identifies itself with when String() is called on it.
func (ic *IRCClient) GetPlugin(name string) (Plugin, bool) {
	return ic.plugins.GetPlugin(name)
}

// Sends a reply to a parsed message from a user. This is mostly intended for plugins
// and will automatically distinguish between channel and query messages. Note: Notice
// replies will currently be sent to the client using PRIVMSG, this may change in the
// future.
func (ic *IRCClient) Reply(cmd *IRCCommand, message string) {
	var target string
	if cmd.Target != ic.GetNick() {
		target = cmd.Target
	} else {
		target = strings.SplitN(cmd.Source, "!", 2)[0]
	}
	ic.SendLine("PRIVMSG " + target + " :" + message)
}
<commit_msg>Fix trigger matching<commit_after>// Package ircclient provides the main interface for library users
// It manages a single connection to the server and the associated
// configuration and plugins.
package ircclient

import (
	"os"
	"strings"
)

type IRCClient struct {
	conn       *IRCConn
	conf       map[string]string
	plugins    *pluginStack
	disconnect chan bool
}

// Returns a new IRCClient connection with the given configuration options.
// It will not connect to the given server until Connect() has been called,
// so you can register plugins before connecting
func NewIRCClient(hostport, nick, rname, ident string, trigger string) *IRCClient {
	c := &IRCClient{nil, make(map[string]string), newPluginStack(), make(chan bool)}
	c.conf["nick"] = nick
	c.conf["hostport"] = hostport
	c.conf["rname"] = rname
	c.conf["ident"] = ident
	c.conf["trigger"] = trigger
	c.RegisterPlugin(&basicProtocol{})
	return c
}

// Registers a new plugin. Plugins can be registered at any time, even before
// the actual connection attempt. The plugin's Unregister() function will already
// be called when the connection is lost.
func (ic *IRCClient) RegisterPlugin(p Plugin) os.Error {
	if _, ok := ic.plugins.GetPlugin(p.String()); ok == true {
		return os.NewError("Plugin already exists")
	}
	p.Register(ic)
	ic.plugins.Push(p)
	return nil
}

// Connects to the server specified on object creation. If the chosen nickname is
// already in use, it will automatically be suffixed with an single underscore until
// an unused nickname is found. This function blocks until the connection attempt
// has been finished.
func (ic *IRCClient) Connect() os.Error {
	ic.conn = NewIRCConn()
	e := ic.conn.Connect(ic.conf["hostport"])
	if e != nil {
		return e
	}
	ic.conn.Output <- "NICK " + ic.conf["nick"]
	ic.conn.Output <- "USER " + ic.conf["ident"] + " * Q :" + ic.conf["rname"]
	nick := ic.conf["nick"]
	for {
		line, ok := <-ic.conn.Input
		if !ok {
			return <-ic.conn.Err
		}

		// Invoke plugin line handlers.
		// At this point, it makes no sense to
		// process "commands". If a plugin needs
		// interaction in this state, it should be
		// low-level.
		s := ParseServerLine(line)
		if s == nil {
			continue
		}
		for p := range ic.plugins.Iter() {
			go p.ProcessLine(s)
		}

		switch s.Command {
		case "433":
			// Nickname already in use
			nick = nick + "_"
			ic.conf["nick"] = nick
			ic.conn.Output <- "NICK " + nick
		case "001":
			// Successfully registered
			return nil
		}
	}
	return nil
}

func (ic *IRCClient) dispatchHandlers(in string) {
	var c *IRCCommand = nil

	s := ParseServerLine(in)
	if s == nil {
		return
	}
	if (s.Command == "PRIVMSG" || s.Command == "NOTICE") && (s.Target == ic.conf["nick"] || string.Index(s.Args[0], ic.conf["trigger"]) == 0) {
		c = ParseCommand(s)
		// Strip trigger, if necessary
		if c != nil && s.Target != ic.conf["nick"] && len(c.Command) != 0 {
			c.Command = c.Command[len(ic.conf["trigger"]):len(c.Command)]
		}
	}

	for p := range ic.plugins.Iter() {
		go p.ProcessLine(s)
		if c != nil {
			go p.ProcessCommand(c)
		}
	}
}

// Starts the actual command processing. This function will block until the connection
// has either been lost or Disconnect() has been called (by a plugin or by the library
// user).
func (ic *IRCClient) InputLoop() os.Error {
	for {
		in, ok := <-ic.conn.Input
		if !ok {
			return <-ic.conn.Err
		}
		ic.dispatchHandlers(in)
	}
	panic("This never happens")
}

// Disconnects from the server with the given quit message. All plugins wil be unregistered
// and pending messages in queue (e.g. because of floodprotection) will be flushed. This will
// also make InputLoop() return.
func (ic *IRCClient) Disconnect(quitmsg string) {
	ic.shutdown()
	ic.conn.Output <- "QUIT :" + quitmsg
	ic.conn.Quit()
}

// Gets one of the configuration options supplied to the NewIRCClient() method. Valid config
// options usually include:
//  - nick
//  - hostport (colon-seperated host and port to connect to)
//  - rname (the real name)
//  - ident
//  - trigger
func (ic *IRCClient) GetConfOpt(option string) string {
	return ic.conf[option]
}

// Sets a configuration option (see also GetConfOpt())
func (ic *IRCClient) SetConfOpt(option string) {
	ic.conf[option] = option
}

// Dumps a raw line to the server socket. This is usually called by plugins, but may also
// be used by the library user.
func (ic *IRCClient) SendLine(line string) {
	ic.conn.Output <- line
}

func (ic *IRCClient) shutdown() {
	for ic.plugins.Size() != 0 {
		p := ic.plugins.Pop()
		p.Unregister()
	}
}

// Gets the current nickname. Note: This is equivalent to a call to GetConfOpt("nick") and
// might be removed in the future. Better use GetConfOpt() for this purpose
func (ic *IRCClient) GetNick() string {
	return ic.conf["nick"]
}

// Returns a channel on which all plugins will be sent. Use it to iterate over all registered
// plugins.
func (ic *IRCClient) IterPlugins() <-chan Plugin {
	return ic.plugins.Iter()
}

// Get the pointer to a specific plugin that has been registered using RegisterPlugin()
// Name is the name the plugin identifies itself with when String() is called on it.
func (ic *IRCClient) GetPlugin(name string) (Plugin, bool) {
	return ic.plugins.GetPlugin(name)
}

// Sends a reply to a parsed message from a user. This is mostly intended for plugins
// and will automatically distinguish between channel and query messages. Note: Notice
// replies will currently be sent to the client using PRIVMSG, this may change in the
// future.
func (ic *IRCClient) Reply(cmd *IRCCommand, message string) {
	var target string
	if cmd.Target != ic.GetNick() {
		target = cmd.Target
	} else {
		target = strings.SplitN(cmd.Source, "!", 2)[0]
	}
	ic.SendLine("PRIVMSG " + target + " :" + message)
}
<|endoftext|>


<commit_before>// +build darwin

package keyring

import (
	"io/ioutil"
	"os"
	"reflect"
	"testing"
)

func TestOSXKeychainDoesntExist(t *testing.T) {
	file := tmpKeychain(t)
	defer os.Remove(file)

	k, err := createKeychain(file, false, "llamas")
	if err != nil {
		t.Fatal(err)
	}
	defer releaseKeychain(k)

	if exists, _ := keychainExists(file); !exists {
		t.Fatalf("Expected existing keychain to be shown as existing")
	}

	if exists, _ := keychainExists("llamaspleasedontbeakeychainwiththisname"); exists {
		t.Fatalf("Expected non-existing keychain to NOT be shown as existing")
	}
}

func TestOSXKeychainKeyringSet(t *testing.T) {
	file := tmpKeychain(t)
	defer os.Remove(file)

	k := &keychain{path: file, passphrase: "llamas", service: "test"}
	item := Item{
		Key:         "llamas",
		Label:       "Arbitrary label",
		Description: "A freetext description",
		Data:        []byte("llamas are great"),
		TrustSelf:   true,
	}

	if err := k.Set(item); err != nil {
		t.Fatal(err)
	}

	v, err := k.Get("llamas")
	if err != nil {
		t.Fatal(err)
	}

	if string(v.Data) != string(item.Data) {
		t.Fatalf("Data stored was not the data retrieved: %q vs %q", v.Data, item.Data)
	}

	if string(v.Key) != item.Key {
		t.Fatalf("Key stored was not the data retrieved: %q vs %q", v.Key, item.Key)
	}

	if string(v.Description) != item.Description {
		t.Fatalf("Description stored was not the data retrieved: %q vs %q", v.Description, item.Description)
	}
}

func TestOSXKeychainKeyringListKeys(t *testing.T) {
	file := tmpKeychain(t)
	defer os.Remove(file)

	k := &keychain{path: file, passphrase: "llamas", service: "test"}
	keys := []string{"key1", "key2", "key3"}

	for _, key := range keys {
		if err := k.Set(Item{Key: key, Data: []byte("llamas are great")}); err != nil {
			t.Fatal(err)
		}
	}

	keys2, err := k.Keys()
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(keys, keys2) {
		t.Fatalf("Retrieved keys weren't the same: %q vs %q", keys, keys2)
	}
}

func tmpKeychain(t *testing.T) (path string) {
	file, err := ioutil.TempFile(os.TempDir(), "aws-vault-test")
	if err != nil {
		t.Fatal(err)
		return
	}
	os.Remove(file.Name())
	return file.Name()
}
<commit_msg>Fix failing tests<commit_after>// +build darwin

package keyring

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"testing"
	"time"

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

var keychainDir string

func deleteKeychain(name string) {
	home, err := homedir.Dir()
	if err != nil {
		panic(err)
	}

	f := filepath.Join(home, "Library/Keychains", name+"-db")
	log.Printf("removing %s", f)

	if err = os.Remove(f); err != nil {
		panic(err)
	}
}

func TestOSXKeychainKeyringSet(t *testing.T) {
	name := tmpKeychain(t)
	defer deleteKeychain(name)

	k := &keychain{
		path:       name,
		passphrase: "llamas",
		service:    "test",
	}

	item := Item{
		Key:         "llamas",
		Label:       "Arbitrary label",
		Description: "A freetext description",
		Data:        []byte("llamas are great"),
		TrustSelf:   true,
	}

	if err := k.Set(item); err != nil {
		t.Fatal(err)
	}

	v, err := k.Get("llamas")
	if err != nil {
		t.Fatal(err)
	}

	if string(v.Data) != string(item.Data) {
		t.Fatalf("Data stored was not the data retrieved: %q vs %q", v.Data, item.Data)
	}

	if string(v.Key) != item.Key {
		t.Fatalf("Key stored was not the data retrieved: %q vs %q", v.Key, item.Key)
	}

	if string(v.Description) != item.Description {
		t.Fatalf("Description stored was not the data retrieved: %q vs %q", v.Description, item.Description)
	}
}

func TestOSXKeychainKeyringListKeys(t *testing.T) {
	name := tmpKeychain(t)
	defer deleteKeychain(name)

	k := &keychain{
		path:       name,
		passphrase: "llamas",
		service:    "test",
	}

	keys := []string{"key1", "key2", "key3"}

	for _, key := range keys {
		item := Item{
			Key:       key,
			Data:      []byte("llamas are great"),
			TrustSelf: true,
		}

		if err := k.Set(item); err != nil {
			t.Fatal(err)
		}
	}

	keys2, err := k.Keys()
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(keys, keys2) {
		t.Fatalf("Retrieved keys weren't the same: %q vs %q", keys, keys2)
	}
}

func tmpKeychain(t *testing.T) (name string) {
	return fmt.Sprintf("aws-vault-test-%d.keychain", time.Now().UnixNano())
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package selinux

import (
	api "k8s.io/kubernetes/pkg/apis/core"
	"k8s.io/kubernetes/pkg/apis/extensions"
	"reflect"
	"strings"
	"testing"
)

func TestMustRunAsOptions(t *testing.T) {
	tests := map[string]struct {
		opts *extensions.SELinuxStrategyOptions
		pass bool
	}{
		"invalid opts": {
			opts: &extensions.SELinuxStrategyOptions{},
			pass: false,
		},
		"valid opts": {
			opts: &extensions.SELinuxStrategyOptions{SELinuxOptions: &api.SELinuxOptions{}},
			pass: true,
		},
	}
	for name, tc := range tests {
		_, err := NewMustRunAs(tc.opts)
		if err != nil && tc.pass {
			t.Errorf("%s expected to pass but received error %#v", name, err)
		}
		if err == nil && !tc.pass {
			t.Errorf("%s expected to fail but did not receive an error", name)
		}
	}
}

func TestMustRunAsGenerate(t *testing.T) {
	opts := &extensions.SELinuxStrategyOptions{
		SELinuxOptions: &api.SELinuxOptions{
			User:  "user",
			Role:  "role",
			Type:  "type",
			Level: "level",
		},
	}
	mustRunAs, err := NewMustRunAs(opts)
	if err != nil {
		t.Fatalf("unexpected error initializing NewMustRunAs %v", err)
	}
	generated, err := mustRunAs.Generate(nil, nil)
	if err != nil {
		t.Fatalf("unexpected error generating selinux %v", err)
	}
	if !reflect.DeepEqual(generated, opts.SELinuxOptions) {
		t.Errorf("generated selinux does not equal configured selinux")
	}
}

func TestMustRunAsValidate(t *testing.T) {
	newValidOpts := func() *api.SELinuxOptions {
		return &api.SELinuxOptions{
			User:  "user",
			Role:  "role",
			Level: "s0:c0,c6",
			Type:  "type",
		}
	}

	newValidOptsWithLevel := func(level string) *api.SELinuxOptions {
		opts := newValidOpts()
		opts.Level = level
		return opts
	}

	role := newValidOpts()
	role.Role = "invalid"

	user := newValidOpts()
	user.User = "invalid"

	level := newValidOpts()
	level.Level = "invalid"

	seType := newValidOpts()
	seType.Type = "invalid"

	tests := map[string]struct {
		seLinux     *api.SELinuxOptions
		expectedMsg string
	}{
		"invalid role": {
			seLinux:     role,
			expectedMsg: "role: Invalid value",
		},
		"invalid user": {
			seLinux:     user,
			expectedMsg: "user: Invalid value",
		},
		"invalid level": {
			seLinux:     level,
			expectedMsg: "level: Invalid value",
		},
		"invalid type": {
			seLinux:     seType,
			expectedMsg: "type: Invalid value",
		},
		"valid": {
			seLinux:     newValidOpts(),
			expectedMsg: "",
		},
		"valid with different order of categories": {
			seLinux:     newValidOptsWithLevel("s0:c6,c0"),
			expectedMsg: "",
		},
	}

	opts := &extensions.SELinuxStrategyOptions{
		SELinuxOptions: newValidOpts(),
	}

	for name, tc := range tests {
		mustRunAs, err := NewMustRunAs(opts)
		if err != nil {
			t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err)
			continue
		}

		errs := mustRunAs.Validate(nil, nil, nil, tc.seLinux)
		//should've passed but didn't
		if len(tc.expectedMsg) == 0 && len(errs) > 0 {
			t.Errorf("%s expected no errors but received %v", name, errs)
		}
		//should've failed but didn't
		if len(tc.expectedMsg) != 0 && len(errs) == 0 {
			t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg)
		}
		//failed with additional messages
		if len(tc.expectedMsg) != 0 && len(errs) > 1 {
			t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs)
		}
		//check that we got the right message
		if len(tc.expectedMsg) != 0 && len(errs) == 1 {
			if !strings.Contains(errs[0].Error(), tc.expectedMsg) {
				t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs)
			}
		}
	}
}
<commit_msg>selinux/mustrunas_test.go(TestMustRunAsValidate): rename a member to make its meaning obvious.<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package selinux

import (
	api "k8s.io/kubernetes/pkg/apis/core"
	"k8s.io/kubernetes/pkg/apis/extensions"
	"reflect"
	"strings"
	"testing"
)

func TestMustRunAsOptions(t *testing.T) {
	tests := map[string]struct {
		opts *extensions.SELinuxStrategyOptions
		pass bool
	}{
		"invalid opts": {
			opts: &extensions.SELinuxStrategyOptions{},
			pass: false,
		},
		"valid opts": {
			opts: &extensions.SELinuxStrategyOptions{SELinuxOptions: &api.SELinuxOptions{}},
			pass: true,
		},
	}
	for name, tc := range tests {
		_, err := NewMustRunAs(tc.opts)
		if err != nil && tc.pass {
			t.Errorf("%s expected to pass but received error %#v", name, err)
		}
		if err == nil && !tc.pass {
			t.Errorf("%s expected to fail but did not receive an error", name)
		}
	}
}

func TestMustRunAsGenerate(t *testing.T) {
	opts := &extensions.SELinuxStrategyOptions{
		SELinuxOptions: &api.SELinuxOptions{
			User:  "user",
			Role:  "role",
			Type:  "type",
			Level: "level",
		},
	}
	mustRunAs, err := NewMustRunAs(opts)
	if err != nil {
		t.Fatalf("unexpected error initializing NewMustRunAs %v", err)
	}
	generated, err := mustRunAs.Generate(nil, nil)
	if err != nil {
		t.Fatalf("unexpected error generating selinux %v", err)
	}
	if !reflect.DeepEqual(generated, opts.SELinuxOptions) {
		t.Errorf("generated selinux does not equal configured selinux")
	}
}

func TestMustRunAsValidate(t *testing.T) {
	newValidOpts := func() *api.SELinuxOptions {
		return &api.SELinuxOptions{
			User:  "user",
			Role:  "role",
			Level: "s0:c0,c6",
			Type:  "type",
		}
	}

	newValidOptsWithLevel := func(level string) *api.SELinuxOptions {
		opts := newValidOpts()
		opts.Level = level
		return opts
	}

	role := newValidOpts()
	role.Role = "invalid"

	user := newValidOpts()
	user.User = "invalid"

	level := newValidOpts()
	level.Level = "invalid"

	seType := newValidOpts()
	seType.Type = "invalid"

	tests := map[string]struct {
		podSeLinux  *api.SELinuxOptions
		expectedMsg string
	}{
		"invalid role": {
			podSeLinux:  role,
			expectedMsg: "role: Invalid value",
		},
		"invalid user": {
			podSeLinux:  user,
			expectedMsg: "user: Invalid value",
		},
		"invalid level": {
			podSeLinux:  level,
			expectedMsg: "level: Invalid value",
		},
		"invalid type": {
			podSeLinux:  seType,
			expectedMsg: "type: Invalid value",
		},
		"valid": {
			podSeLinux:  newValidOpts(),
			expectedMsg: "",
		},
		"valid with different order of categories": {
			podSeLinux:  newValidOptsWithLevel("s0:c6,c0"),
			expectedMsg: "",
		},
	}

	opts := &extensions.SELinuxStrategyOptions{
		SELinuxOptions: newValidOpts(),
	}

	for name, tc := range tests {
		mustRunAs, err := NewMustRunAs(opts)
		if err != nil {
			t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err)
			continue
		}

		errs := mustRunAs.Validate(nil, nil, nil, tc.podSeLinux)
		//should've passed but didn't
		if len(tc.expectedMsg) == 0 && len(errs) > 0 {
			t.Errorf("%s expected no errors but received %v", name, errs)
		}
		//should've failed but didn't
		if len(tc.expectedMsg) != 0 && len(errs) == 0 {
			t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg)
		}
		//failed with additional messages
		if len(tc.expectedMsg) != 0 && len(errs) > 1 {
			t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs)
		}
		//check that we got the right message
		if len(tc.expectedMsg) != 0 && len(errs) == 1 {
			if !strings.Contains(errs[0].Error(), tc.expectedMsg) {
				t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs)
			}
		}
	}
}
<|endoftext|>


<commit_before>package kit

import (
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"gopkg.in/fsnotify.v1"
)

const (
	textFixturePath  = "../fixtures/project/whatever.txt"
	watchFixturePath = "../fixtures/project"
)

type FileWatcherTestSuite struct {
	suite.Suite
	watcher *FileWatcher
}

func (suite *FileWatcherTestSuite) TestNewFileReader() {
	watcher, err := newFileWatcher(ThemeClient{}, watchFixturePath, true, eventFilter{}, func(ThemeClient, Asset, EventType, error) {})
	assert.Nil(suite.T(), err)
	assert.Equal(suite.T(), true, watcher.IsWatching())
	watcher.StopWatching()
}

func (suite *FileWatcherTestSuite) TestConvertFsEvents() {
	assetChan := make(chan Asset, 4)
	eventChan := make(chan fsnotify.Event)
	var wg sync.WaitGroup
	wg.Add(2)

	newWatcher := &FileWatcher{
		done:    make(chan bool),
		watcher: &fsnotify.Watcher{Events: eventChan},
	}

	newWatcher.callback = func(client ThemeClient, asset Asset, event EventType, err error) {
		assert.Nil(suite.T(), err)
		assert.Equal(suite.T(), Update, event)
		assetChan <- asset
		wg.Done()
	}

	go convertFsEvents(newWatcher)

	go func() {
		writes := []fsnotify.Event{
			{Name: watchFixturePath + "/templates/template.liquid", Op: fsnotify.Write},
			{Name: watchFixturePath + "/templates/template.liquid", Op: fsnotify.Write},
			{Name: watchFixturePath + "/templates/template.liquid", Op: fsnotify.Write},
			{Name: watchFixturePath + "/templates/customers/test.liquid", Op: fsnotify.Write},
		}
		for _, fsEvent := range writes {
			eventChan <- fsEvent
		}
		close(eventChan)
	}()

	wg.Wait()
	// test that the events are debounced
	assert.Equal(suite.T(), 2, len(assetChan))
}

func (suite *FileWatcherTestSuite) TestCallbackEvents() {
	events := map[string]fsnotify.Event{
		watchFixturePath + "/templates/template.liquid": {Name: watchFixturePath + "/templates/template.liquid", Op: fsnotify.Write},
	}

	var wg sync.WaitGroup
	wg.Add(len(events))

	newWatcher := &FileWatcher{callback: func(client ThemeClient, asset Asset, event EventType, err error) {
		assert.Nil(suite.T(), err)
		assert.Equal(suite.T(), Asset{Key: "templates/template.liquid", Value: ""}, asset)
		assert.Equal(suite.T(), Update, event)
		wg.Done()
	}}

	callbackEvents(newWatcher, events)

	newWatcher = &FileWatcher{callback: func(client ThemeClient, asset Asset, event EventType, err error) {
		assert.NotNil(suite.T(), err)
		wg.Done()
	}}

	events = map[string]fsnotify.Event{
		"nope/template.liquid": {Name: "nope/template.liquid", Op: fsnotify.Write},
	}
	wg.Add(len(events))

	callbackEvents(newWatcher, events)

	wg.Wait()
}

func (suite *FileWatcherTestSuite) TestStopWatching() {
	watcher, err := newFileWatcher(ThemeClient{}, watchFixturePath, true, eventFilter{}, func(ThemeClient, Asset, EventType, error) {})
	assert.Nil(suite.T(), err)
	assert.Equal(suite.T(), true, watcher.IsWatching())
	watcher.StopWatching()
	time.Sleep(50 * time.Millisecond)
	assert.Equal(suite.T(), false, watcher.IsWatching())
}

func (suite *FileWatcherTestSuite) TestHandleEvent() {
	writes := []fsnotify.Op{
		fsnotify.Create,
		fsnotify.Write,
		fsnotify.Remove,
	}

	var wg sync.WaitGroup
	wg.Add(len(writes))

	watcher := &FileWatcher{callback: func(client ThemeClient, asset Asset, event EventType, err error) {
		assert.Equal(suite.T(), "File not in project workspace.", err.Error())
		wg.Done()
	}}

	for _, fsEvent := range writes {
		handleEvent(watcher, fsnotify.Event{Name: textFixturePath, Op: fsEvent})
	}

	wg.Wait()
}

func (suite *FileWatcherTestSuite) TestExtractAssetKey() {
	tests := map[string]string{
		textFixturePath:                                 "",
		"/long/path/to/config.yml":                      "",
		"/long/path/to/assets/logo.png":                 "assets/logo.png",
		"/long/path/to/templates/customers/test.liquid": "templates/customers/test.liquid",
		"/long/path/to/config/test.liquid":              "config/test.liquid",
		"/long/path/to/layout/test.liquid":              "layout/test.liquid",
		"/long/path/to/snippets/test.liquid":            "snippets/test.liquid",
		"/long/path/to/templates/test.liquid":           "templates/test.liquid",
		"/long/path/to/locales/test.liquid":             "locales/test.liquid",
		"/long/path/to/sections/test.liquid":            "sections/test.liquid",
	}
	for input, expected := range tests {
		assert.Equal(suite.T(), expected, extractAssetKey(input))
	}
}

func (suite *FileWatcherTestSuite) TestfindDirectoriesToWatch() {
	expected := []string{
		clean(watchFixturePath),
		clean(watchFixturePath + "/assets"),
		clean(watchFixturePath + "/config"),
		clean(watchFixturePath + "/layout"),
		clean(watchFixturePath + "/locales"),
		clean(watchFixturePath + "/snippets"),
		clean(watchFixturePath + "/templates"),
		clean(watchFixturePath + "/templates/customers"),
	}

	files := findDirectoriesToWatch(watchFixturePath, true, func(string) bool { return false })
	assert.Equal(suite.T(), expected, files)

	files = findDirectoriesToWatch(watchFixturePath, false, func(string) bool { return false })
	assert.Equal(suite.T(), []string{watchFixturePath}, files)
}

func TestFileWatcherTestSuite(t *testing.T) {
	suite.Run(t, new(FileWatcherTestSuite))
}

func clean(path string) string {
	return filepath.Clean(path)
}
<commit_msg>fixing windows test<commit_after>package kit

import (
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"gopkg.in/fsnotify.v1"
)

const (
	textFixturePath  = "../fixtures/project/whatever.txt"
	watchFixturePath = "../fixtures/project"
)

type FileWatcherTestSuite struct {
	suite.Suite
	watcher *FileWatcher
}

func (suite *FileWatcherTestSuite) TestNewFileReader() {
	watcher, err := newFileWatcher(ThemeClient{}, watchFixturePath, true, eventFilter{}, func(ThemeClient, Asset, EventType, error) {})
	assert.Nil(suite.T(), err)
	assert.Equal(suite.T(), true, watcher.IsWatching())
	watcher.StopWatching()
}

func (suite *FileWatcherTestSuite) TestConvertFsEvents() {
	assetChan := make(chan Asset, 4)
	eventChan := make(chan fsnotify.Event)
	var wg sync.WaitGroup
	wg.Add(2)

	newWatcher := &FileWatcher{
		done:    make(chan bool),
		watcher: &fsnotify.Watcher{Events: eventChan},
	}

	newWatcher.callback = func(client ThemeClient, asset Asset, event EventType, err error) {
		assert.Nil(suite.T(), err)
		assert.Equal(suite.T(), Update, event)
		assetChan <- asset
		wg.Done()
	}

	go convertFsEvents(newWatcher)

	go func() {
		writes := []fsnotify.Event{
			{Name: watchFixturePath + "/templates/template.liquid", Op: fsnotify.Write},
			{Name: watchFixturePath + "/templates/template.liquid", Op: fsnotify.Write},
			{Name: watchFixturePath + "/templates/template.liquid", Op: fsnotify.Write},
			{Name: watchFixturePath + "/templates/customers/test.liquid", Op: fsnotify.Write},
		}
		for _, fsEvent := range writes {
			eventChan <- fsEvent
		}
		close(eventChan)
	}()

	wg.Wait()
	// test that the events are debounced
	assert.Equal(suite.T(), 2, len(assetChan))
}

func (suite *FileWatcherTestSuite) TestCallbackEvents() {
	events := map[string]fsnotify.Event{
		watchFixturePath + "/templates/template.liquid": {Name: watchFixturePath + "/templates/template.liquid", Op: fsnotify.Write},
	}

	var wg sync.WaitGroup
	wg.Add(len(events))

	newWatcher := &FileWatcher{callback: func(client ThemeClient, asset Asset, event EventType, err error) {
		assert.Nil(suite.T(), err)
		assert.Equal(suite.T(), Asset{Key: "templates/template.liquid", Value: ""}, asset)
		assert.Equal(suite.T(), Update, event)
		wg.Done()
	}}

	callbackEvents(newWatcher, events)

	newWatcher = &FileWatcher{callback: func(client ThemeClient, asset Asset, event EventType, err error) {
		assert.NotNil(suite.T(), err)
		wg.Done()
	}}

	events = map[string]fsnotify.Event{
		"nope/template.liquid": {Name: "nope/template.liquid", Op: fsnotify.Write},
	}
	wg.Add(len(events))

	callbackEvents(newWatcher, events)

	wg.Wait()
}

func (suite *FileWatcherTestSuite) TestStopWatching() {
	watcher, err := newFileWatcher(ThemeClient{}, watchFixturePath, true, eventFilter{}, func(ThemeClient, Asset, EventType, error) {})
	assert.Nil(suite.T(), err)
	assert.Equal(suite.T(), true, watcher.IsWatching())
	watcher.StopWatching()
	time.Sleep(50 * time.Millisecond)
	assert.Equal(suite.T(), false, watcher.IsWatching())
}

func (suite *FileWatcherTestSuite) TestHandleEvent() {
	writes := []fsnotify.Op{
		fsnotify.Create,
		fsnotify.Write,
		fsnotify.Remove,
	}

	var wg sync.WaitGroup
	wg.Add(len(writes))

	watcher := &FileWatcher{callback: func(client ThemeClient, asset Asset, event EventType, err error) {
		assert.Equal(suite.T(), "File not in project workspace.", err.Error())
		wg.Done()
	}}

	for _, fsEvent := range writes {
		handleEvent(watcher, fsnotify.Event{Name: textFixturePath, Op: fsEvent})
	}

	wg.Wait()
}

func (suite *FileWatcherTestSuite) TestExtractAssetKey() {
	tests := map[string]string{
		textFixturePath:                                 "",
		"/long/path/to/config.yml":                      "",
		"/long/path/to/assets/logo.png":                 "assets/logo.png",
		"/long/path/to/templates/customers/test.liquid": "templates/customers/test.liquid",
		"/long/path/to/config/test.liquid":              "config/test.liquid",
		"/long/path/to/layout/test.liquid":              "layout/test.liquid",
		"/long/path/to/snippets/test.liquid":            "snippets/test.liquid",
		"/long/path/to/templates/test.liquid":           "templates/test.liquid",
		"/long/path/to/locales/test.liquid":             "locales/test.liquid",
		"/long/path/to/sections/test.liquid":            "sections/test.liquid",
	}
	for input, expected := range tests {
		assert.Equal(suite.T(), expected, extractAssetKey(input))
	}
}

func (suite *FileWatcherTestSuite) TestfindDirectoriesToWatch() {
	expected := []string{
		clean(watchFixturePath),
		clean(watchFixturePath + "/assets"),
		clean(watchFixturePath + "/config"),
		clean(watchFixturePath + "/layout"),
		clean(watchFixturePath + "/locales"),
		clean(watchFixturePath + "/snippets"),
		clean(watchFixturePath + "/templates"),
		clean(watchFixturePath + "/templates/customers"),
	}

	files := findDirectoriesToWatch(watchFixturePath, true, func(string) bool { return false })
	assert.Equal(suite.T(), expected, files)

	files = findDirectoriesToWatch(watchFixturePath, false, func(string) bool { return false })
	assert.Equal(suite.T(), []string{clean(watchFixturePath)}, files)
}

func TestFileWatcherTestSuite(t *testing.T) {
	suite.Run(t, new(FileWatcherTestSuite))
}

func clean(path string) string {
	return filepath.Clean(path)
}
<|endoftext|>


<commit_before>// Copyright 2015 Dmitry Vyukov. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package main

import (
	"bytes"
	"github.com/dvyukov/go-fuzz/gen"

	"github.com/go-spatial/tegola/geom/encoding/wkb/internal/tcase"
)

func main() {
	createFromTestData()
}

func createFromTestData() []byte {
	fnames, err := tcase.GetFiles("testdata")
	if err != nil {
		t.Fatalf("error getting files: %v", err)
	}
	var fname string

	fn := func(idx int, tc tcase.C) {
		gen.Emit(tc.Expected, nil, true)
	}

	for _, fname = range fnames {
		cases, err := tcase.ParseFile(fname)
		if err != nil {
			t.Fatalf("error parsing file: %v : %v ", fname, err)
		}
		for i := range cases {
			gen.Emit(cases[i].Expected, nil, true)
		}
	}

}
<commit_msg>updated testdata generator<commit_after>// Copyright 2015 Dmitry Vyukov. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package main

import (
	"github.com/dvyukov/go-fuzz/gen"

	"github.com/go-spatial/tegola/geom/encoding/wkb/internal/tcase"
)

func main() {
	createFromTestData()
}

func createFromTestData() {
	fnames, _ := tcase.GetFiles("testdata")
	var fname string

	for _, fname = range fnames {
		cases, _ := tcase.ParseFile(fname)
		for i := range cases {
			gen.Emit(cases[i].Bytes, nil, true)
		}
	}

}
<|endoftext|>


<commit_before>package cmd

import (
	"context"
	"fmt"
	"strings"

	"github.com/google/go-github/github"
	"github.com/spf13/cobra"
)

// CreateRepoOptions holds options for creating a repo.
type CreateRepoOptions struct {
	Name      string
	IsPrivate string
}

var createRepoOptions = &CreateRepoOptions{}
var createRepoCmd = &cobra.Command{
	Use:   "repo [name]",
	Short: "Create repo",
	Long:  `Creates a Github repo.`,
	Run: func(cmd *cobra.Command, args []string) {
		err := runCreateRepo(cmd, args, createRepoOptions)
		if err != nil {
			exitWithError(err)
		}
	},
}

func runCreateRepo(cmd *cobra.Command, args []string, c *CreateRepoOptions) error {
	if len(args) != 1 {
		return cmd.Help()
	}
	repoName := args[0]

	ctx := context.Background()
	client, err := gc.GetClient(ctx)
	if err != nil {
		return err
	}
	repo := &github.Repository{
		Name:    github.String(repoName),
		Private: github.Bool(false),
	}
	_, _, err = client.Repositories.Create(ctx, "", repo)
	if err != nil {
		if strings.Fields(err.Error())[2] == "422" {
			exitWithError(fmt.Errorf("Repo %s already exists", repoName))
		}
		exitWithError(err)
	}
	fmt.Printf("Repo %s created in github.\n", repoName)
	return nil
}
<commit_msg>cleanup create_repo<commit_after>package cmd

import (
	"context"
	"fmt"

	"github.com/google/go-github/github"
	"github.com/spf13/cobra"
)

// CreateRepoOptions holds options for creating a repo.
type CreateRepoOptions struct {
	Name      string
	IsPrivate string
}

var createRepoOptions = &CreateRepoOptions{}
var createRepoCmd = &cobra.Command{
	Use:   "repo [name]",
	Short: "Create repo",
	Long:  `Creates a Github repo.`,
	Run: func(cmd *cobra.Command, args []string) {
		err := runCreateRepo(cmd, args, createRepoOptions)
		if err != nil {
			exitWithError(err)
		}
	},
}

func runCreateRepo(cmd *cobra.Command, args []string, c *CreateRepoOptions) error {
	if len(args) != 1 {
		return cmd.Help()
	}
	repoName := args[0]

	ctx := context.Background()
	client, err := gc.GetClient(ctx)
	if err != nil {
		return err
	}

	repo := &github.Repository{
		Name:    github.String(repoName),
		Private: github.Bool(false),
	}
	_, _, err = client.Repositories.Create(ctx, "", repo)
	if err != nil {
		// Convert error to github.ErrorResponse. Since "repo already exists" is
		// a custom response, e.Code is "custom", which doesn't tell the exact
		// reason. Hence, compare the error message to ensure it's an already
		// existing repo error.
		// https://developer.github.com/v3/#client-errors
		e := err.(*github.ErrorResponse).Errors
		if len(e) > 0 {
			if e[0].Message == "name already exists on this account" {
				return fmt.Errorf("repo %s already exists", repoName)
			}
		}
		return err
	}
	fmt.Printf("Repo %s created in github.\n", repoName)
	return nil
}
<|endoftext|>