<commit_before>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
//
// 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 index

import (
	"fmt"
	"github.com/tomachalek/gloomy/wstore"
	"strings"
)

type NgramResultItem struct {
	next  *NgramResultItem
	ngram []int
}

type NgramSearchResult struct {
	first *NgramResultItem
	curr  *NgramResultItem
	size  int
}

func (nsr *NgramSearchResult) GetSize() int {
	return nsr.size
}

func (nsr *NgramSearchResult) ResetCursor() {
	nsr.curr = nsr.first
}

func (nsr *NgramSearchResult) HasNext() bool {
	return nsr.curr != nil
}

func (nsr *NgramSearchResult) Next() []int {
	ans := nsr.curr
	nsr.curr = nsr.curr.next
	return ans.ngram
}

func (nsr *NgramSearchResult) addValue(ngram []int) {
	item := &NgramResultItem{ngram: ngram}
	if nsr.first == nil {
		nsr.first = item
	}
	if nsr.curr != nil {
		nsr.curr.next = item
	}
	nsr.curr = item
	nsr.size++
}

// --------------------------------------------------------------------

type IndexItem struct {
	index int
	upTo  int
}

type IndexColumn []*IndexItem

type NgramIndex struct {
	values []IndexColumn
}

func (n *NgramIndex) GetInfo() string {
	sizes := make([]string, len(n.values))
	for i, v := range n.values {
		sizes[i] = fmt.Sprintf("%d", len(v))
	}
	return fmt.Sprintf("NgramIndex, num cols: %d, sizes %s", len(n.values), strings.Join(sizes, ", "))
}

func (n *NgramIndex) GetNgramsAt(position int) *NgramSearchResult {
	result := &NgramSearchResult{}
	n.getNextTokenRecords(0, position, position, make([]int, 0), result)
	result.ResetCursor()
	return result
}

func (n *NgramIndex) getNextTokenRecords(colIdx int, fromRow int, toRow int, prevTokens []int, result *NgramSearchResult) {
	col := n.values[colIdx]
	for i := fromRow; i <= toRow; i++ {
		idx := col[i]
		currNgram := append(prevTokens, idx.index)
		if colIdx == len(n.values)-1 {
			result.addValue(currNgram)

		} else {
			nextFromIdx := 0
			if fromRow > 0 {
				nextFromIdx = col[i-1].upTo + 1
			}
			nextToIdx := idx.upTo
			n.getNextTokenRecords(colIdx+1, nextFromIdx, nextToIdx, currNgram, result)
		}
	}
}

func NewNgramIndex(ngramSize int, initialLength int) *NgramIndex {
	ans := &NgramIndex{
		values: make([]IndexColumn, ngramSize),
	}
	for i := range ans.values {
		ans.values[i] = make(IndexColumn, initialLength)
	}
	return ans
}

// ----------------------------------------------------------------------------

type SearchableIndex struct {
	index  *NgramIndex
	wstore *wstore.WordIndex
}

func (si *SearchableIndex) GetNgramsOf(word string) [][]string {
	var ans [][]string
	w := si.wstore.Find(word)
	if w == -1 {
		return ans
	}
	result := si.index.GetNgramsAt(w)
	ans = make([][]string, result.GetSize())
	for i := 0; result.HasNext(); i++ {
		tmp := result.Next()
		ans[i] = si.wstore.DecodeNgram(tmp)
	}
	return ans
}

func OpenSearchableIndex(index *NgramIndex, wstore *wstore.WordIndex) *SearchableIndex {
	return &SearchableIndex{index: index, wstore: wstore}
}

// ----------------------------------------------------------------------------

// DynamicNgramIndex allows adding items to the index
type DynamicNgramIndex struct {
	index         *NgramIndex
	cursors       []int
	initialLength int
}

func NewDynamicNgramIndex(ngramSize int, initialLength int) *DynamicNgramIndex {
	cursors := make([]int, ngramSize)
	for i := range cursors {
		cursors[i] = -1
	}
	return &DynamicNgramIndex{
		initialLength: initialLength,
		index:         NewNgramIndex(ngramSize, initialLength),
		cursors:       cursors,
	}
}

func (nib *DynamicNgramIndex) GetIndex() *NgramIndex {
	return nib.index
}

func (nib *DynamicNgramIndex) GetInfo() string {
	return nib.index.GetInfo()
}

func (nib *DynamicNgramIndex) GetNgramsAt(position int) *NgramSearchResult {
	return nib.index.GetNgramsAt(position)
}

func (nib *DynamicNgramIndex) AddNgram(ngram []int) {
	for i := len(ngram) - 1; i >= 0; i-- {
		nib.addValue(i, ngram[i])
	}
}

func (nib *DynamicNgramIndex) Finish() {
	for i, v := range nib.index.values {
		nib.index.values[i] = v[:nib.cursors[i]]
	}
}

func (nib *DynamicNgramIndex) addValue(tokenPos int, index int) {
	col := nib.index.values[tokenPos]
	if nib.cursors[tokenPos] >= len(col)-1 {
		nib.index.values[tokenPos] = append(col, make(IndexColumn, nib.initialLength/2)...)
		col = nib.index.values[tokenPos]
	}
	upTo := 0
	if tokenPos < len(nib.cursors)-1 {
		upTo = nib.cursors[tokenPos+1]
	}
	if nib.cursors[tokenPos] == -1 || nib.index.values[tokenPos][nib.cursors[tokenPos]].index != index {
		nib.cursors[tokenPos]++
		col[nib.cursors[tokenPos]] = &IndexItem{index: index, upTo: upTo}

	} else {
		if tokenPos > 0 {
			col[nib.cursors[tokenPos]].upTo++
		}
	}
}
<commit_msg>Fix index building (first column problem)<commit_after>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
//
// 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 index

import (
	"fmt"
	"github.com/tomachalek/gloomy/wstore"
	"sort"
	"strings"
)

type NgramResultItem struct {
	next  *NgramResultItem
	ngram []int
}

type NgramSearchResult struct {
	first *NgramResultItem
	curr  *NgramResultItem
	size  int
}

func (nsr *NgramSearchResult) GetSize() int {
	return nsr.size
}

func (nsr *NgramSearchResult) ResetCursor() {
	nsr.curr = nsr.first
}

func (nsr *NgramSearchResult) HasNext() bool {
	return nsr.curr != nil
}

func (nsr *NgramSearchResult) Next() []int {
	ans := nsr.curr
	nsr.curr = nsr.curr.next
	return ans.ngram
}

func (nsr *NgramSearchResult) addValue(ngram []int) {
	item := &NgramResultItem{ngram: ngram}
	if nsr.first == nil {
		nsr.first = item
	}
	if nsr.curr != nil {
		nsr.curr.next = item
	}
	nsr.curr = item
	nsr.size++
}

// --------------------------------------------------------------------

type IndexItem struct {
	index int
	upTo  int
}

type IndexColumn []*IndexItem

type NgramIndex struct {
	values []IndexColumn
}

func (n *NgramIndex) GetInfo() string {
	sizes := make([]string, len(n.values))
	for i, v := range n.values {
		sizes[i] = fmt.Sprintf("%d", len(v))
	}
	return fmt.Sprintf("NgramIndex, num cols: %d, sizes %s", len(n.values), strings.Join(sizes, ", "))
}

func (n *NgramIndex) GetNgramsAt(position int) *NgramSearchResult {
	result := &NgramSearchResult{}
	n.getNextTokenRecords(0, position, position, make([]int, 0), result)
	result.ResetCursor()
	return result
}

func (n *NgramIndex) getNextTokenRecords(colIdx int, fromRow int, toRow int, prevTokens []int, result *NgramSearchResult) {
	col := n.values[colIdx]
	for i := fromRow; i <= toRow; i++ {
		idx := col[i]
		currNgram := append(prevTokens, idx.index)
		if colIdx == len(n.values)-1 {
			result.addValue(currNgram)

		} else {
			nextFromIdx := 0
			if fromRow > 0 {
				nextFromIdx = col[i-1].upTo + 1
			}
			nextToIdx := idx.upTo
			n.getNextTokenRecords(colIdx+1, nextFromIdx, nextToIdx, currNgram, result)
		}
	}
}

func NewNgramIndex(ngramSize int, initialLength int) *NgramIndex {
	ans := &NgramIndex{
		values: make([]IndexColumn, ngramSize),
	}
	for i := range ans.values {
		ans.values[i] = make(IndexColumn, initialLength)
	}
	return ans
}

// ----------------------------------------------------------------------------

type SearchableIndex struct {
	index  *NgramIndex
	wstore *wstore.WordIndex
}

func (si *SearchableIndex) GetNgramsOf(word string) [][]string {
	var ans [][]string
	w := si.wstore.Find(word)
	col0Idx := sort.Search(len(si.index.values[0]), func(i int) bool {
		return si.index.values[0][i].index >= w
	})
	if col0Idx == len(si.index.values[0]) {
		return ans
	}
	result := si.index.GetNgramsAt(col0Idx)
	ans = make([][]string, result.GetSize())
	for i := 0; result.HasNext(); i++ {
		tmp := result.Next()
		ans[i] = si.wstore.DecodeNgram(tmp)
	}
	return ans
}

func OpenSearchableIndex(index *NgramIndex, wstore *wstore.WordIndex) *SearchableIndex {
	return &SearchableIndex{index: index, wstore: wstore}
}

// ----------------------------------------------------------------------------

// DynamicNgramIndex allows adding items to the index
type DynamicNgramIndex struct {
	index         *NgramIndex
	cursors       []int
	initialLength int
}

func NewDynamicNgramIndex(ngramSize int, initialLength int) *DynamicNgramIndex {
	cursors := make([]int, ngramSize)
	for i := range cursors {
		cursors[i] = -1
	}
	return &DynamicNgramIndex{
		initialLength: initialLength,
		index:         NewNgramIndex(ngramSize, initialLength),
		cursors:       cursors,
	}
}

func (nib *DynamicNgramIndex) GetIndex() *NgramIndex {
	return nib.index
}

func (nib *DynamicNgramIndex) GetInfo() string {
	return nib.index.GetInfo()
}

func (nib *DynamicNgramIndex) GetNgramsAt(position int) *NgramSearchResult {
	return nib.index.GetNgramsAt(position)
}

func (nib *DynamicNgramIndex) AddNgram(ngram []int) {
	for i := len(ngram) - 1; i >= 0; i-- {
		nib.addValue(i, ngram[i])
	}
}

func (nib *DynamicNgramIndex) Finish() {
	for i, v := range nib.index.values {
		nib.index.values[i] = v[:nib.cursors[i]]
	}
}

func (nib *DynamicNgramIndex) addValue(tokenPos int, index int) {
	col := nib.index.values[tokenPos]
	if nib.cursors[tokenPos] >= len(col)-1 {
		nib.index.values[tokenPos] = append(col, make(IndexColumn, nib.initialLength/2)...)
		col = nib.index.values[tokenPos]
	}
	upTo := 0
	if tokenPos < len(nib.cursors)-1 {
		upTo = nib.cursors[tokenPos+1]
	}
	if nib.cursors[tokenPos] == -1 || nib.index.values[tokenPos][nib.cursors[tokenPos]].index != index {
		nib.cursors[tokenPos]++
		col[nib.cursors[tokenPos]] = &IndexItem{index: index, upTo: upTo}

	} else {
		col[nib.cursors[tokenPos]].upTo++
	}
}
<|endoftext|>


<commit_before>/*
Package inflection pluralizes and singularizes English nouns.

		inflection.Plural("person") => "people"
		inflection.Plural("Person") => "People"
		inflection.Plural("PERSON") => "PEOPLE"

		inflection.Singular("people") => "person"
		inflection.Singular("People") => "Person"
		inflection.Singular("PEOPLE") => "PERSON"

		inflection.Plural("FancyPerson") => "FancydPeople"
		inflection.Singular("FancyPeople") => "FancydPerson"

Standard rules are from Rails's ActiveSupport (https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb)

If you want to register more rules, follow:

		inflection.AddUncountable("fish")
		inflection.AddIrregular("person", "people")
		inflection.AddPlural("(bu)s$", "${1}ses") # "bus" => "buses" / "BUS" => "BUSES" / "Bus" => "Buses"
		inflection.AddSingular("(bus)(es)?$", "${1}") # "buses" => "bus" / "Buses" => "Bus" / "BUSES" => "BUS"
*/
package inflection

import (
	"regexp"
	"strings"
)

type inflection struct {
	regexp  *regexp.Regexp
	replace string
}

// Regular is a regexp find replace inflection
type Regular struct {
	find    string
	replace string
}

// Irregular is a hard replace inflection,
// containing both singular and plural forms
type Irregular struct {
	singular string
	plural   string
}

// RegularSlice is a slice of Regular inflections
type RegularSlice []Regular

// IrregularSlice is a slice of Irregular inflections
type IrregularSlice []Irregular

var pluralInflections = RegularSlice{
	{"([a-z])$", "${1}s"},
	{"s$", "s"},
	{"^(ax|test)is$", "${1}es"},
	{"(octop|vir)us$", "${1}i"},
	{"(octop|vir)i$", "${1}i"},
	{"(alias|status)$", "${1}es"},
	{"(bu)s$", "${1}ses"},
	{"(buffal|tomat)o$", "${1}oes"},
	{"([ti])um$", "${1}a"},
	{"([ti])a$", "${1}a"},
	{"sis$", "ses"},
	{"(?:([^f])fe|([lr])f)$", "${1}${2}ves"},
	{"(hive)$", "${1}s"},
	{"([^aeiouy]|qu)y$", "${1}ies"},
	{"(x|ch|ss|sh)$", "${1}es"},
	{"(matr|vert|ind)(?:ix|ex)$", "${1}ices"},
	{"^(m|l)ouse$", "${1}ice"},
	{"^(m|l)ice$", "${1}ice"},
	{"^(ox)$", "${1}en"},
	{"^(oxen)$", "${1}"},
	{"(quiz)$", "${1}zes"},
}

var singularInflections = RegularSlice{
	{"s$", ""},
	{"(ss)$", "${1}"},
	{"(n)ews$", "${1}ews"},
	{"([ti])a$", "${1}um"},
	{"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$", "${1}sis"},
	{"(^analy)(sis|ses)$", "${1}sis"},
	{"([^f])ves$", "${1}fe"},
	{"(hive)s$", "${1}"},
	{"(tive)s$", "${1}"},
	{"([lr])ves$", "${1}f"},
	{"([^aeiouy]|qu)ies$", "${1}y"},
	{"(s)eries$", "${1}eries"},
	{"(m)ovies$", "${1}ovie"},
	{"(c)ookies$", "${1}ookie"},
	{"(x|ch|ss|sh)es$", "${1}"},
	{"^(m|l)ice$", "${1}ouse"},
	{"(bus)(es)?$", "${1}"},
	{"(o)es$", "${1}"},
	{"(shoe)s$", "${1}"},
	{"(cris|test)(is|es)$", "${1}is"},
	{"^(a)x[ie]s$", "${1}xis"},
	{"(octop|vir)(us|i)$", "${1}us"},
	{"(alias|status)(es)?$", "${1}"},
	{"^(ox)en", "${1}"},
	{"(vert|ind)ices$", "${1}ex"},
	{"(matr)ices$", "${1}ix"},
	{"(quiz)zes$", "${1}"},
	{"(database)s$", "${1}"},
}

var irregularInflections = IrregularSlice{
	{"person", "people"},
	{"man", "men"},
	{"child", "children"},
	{"sex", "sexes"},
	{"move", "moves"},
	{"mombie", "mombies"},
}

var uncountableInflections = []string{"equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "jeans", "police"}

var compiledPluralMaps []inflection
var compiledSingularMaps []inflection

func compile() {
	compiledPluralMaps = []inflection{}
	compiledSingularMaps = []inflection{}
	for _, uncountable := range uncountableInflections {
		inf := inflection{
			regexp:  regexp.MustCompile("^(?i)(" + uncountable + ")$"),
			replace: "${1}",
		}
		compiledPluralMaps = append(compiledPluralMaps, inf)
		compiledSingularMaps = append(compiledSingularMaps, inf)
	}

	for _, value := range irregularInflections {
		infs := []inflection{
			inflection{regexp: regexp.MustCompile(strings.ToUpper(value.singular) + "$"), replace: strings.ToUpper(value.plural)},
			inflection{regexp: regexp.MustCompile(strings.Title(value.singular) + "$"), replace: strings.Title(value.plural)},
			inflection{regexp: regexp.MustCompile(value.singular + "$"), replace: value.plural},
		}
		compiledPluralMaps = append(compiledPluralMaps, infs...)
	}

	for _, value := range irregularInflections {
		infs := []inflection{
			inflection{regexp: regexp.MustCompile(strings.ToUpper(value.plural) + "$"), replace: strings.ToUpper(value.singular)},
			inflection{regexp: regexp.MustCompile(strings.Title(value.plural) + "$"), replace: strings.Title(value.singular)},
			inflection{regexp: regexp.MustCompile(value.plural + "$"), replace: value.singular},
		}
		compiledSingularMaps = append(compiledSingularMaps, infs...)
	}

	for i := len(pluralInflections) - 1; i >= 0; i-- {
		value := pluralInflections[i]
		infs := []inflection{
			inflection{regexp: regexp.MustCompile(strings.ToUpper(value.find)), replace: strings.ToUpper(value.replace)},
			inflection{regexp: regexp.MustCompile(value.find), replace: value.replace},
			inflection{regexp: regexp.MustCompile("(?i)" + value.find), replace: value.replace},
		}
		compiledPluralMaps = append(compiledPluralMaps, infs...)
	}

	for i := len(singularInflections) - 1; i >= 0; i-- {
		value := singularInflections[i]
		infs := []inflection{
			inflection{regexp: regexp.MustCompile(strings.ToUpper(value.find)), replace: strings.ToUpper(value.replace)},
			inflection{regexp: regexp.MustCompile(value.find), replace: value.replace},
			inflection{regexp: regexp.MustCompile("(?i)" + value.find), replace: value.replace},
		}
		compiledSingularMaps = append(compiledSingularMaps, infs...)
	}
}

func init() {
	compile()
}

// AddPlural adds a plural inflection
func AddPlural(find, replace string) {
	pluralInflections = append(pluralInflections, Regular{find, replace})
	compile()
}

// AddSingular adds a singular inflection
func AddSingular(find, replace string) {
	singularInflections = append(singularInflections, Regular{find, replace})
	compile()
}

// AddIrregular adds an irregular inflection
func AddIrregular(singular, plural string) {
	irregularInflections = append(irregularInflections, Irregular{singular, plural})
	compile()
}

// AddUncountable adds an uncountable inflection
func AddUncountable(values ...string) {
	uncountableInflections = append(uncountableInflections, values...)
	compile()
}

// GetPlural retrieves the plural inflection values
func GetPlural() RegularSlice {
	plurals := make(RegularSlice, len(pluralInflections))
	copy(plurals, pluralInflections)
	return plurals
}

// GetSingular retrieves the singular inflection values
func GetSingular() RegularSlice {
	singulars := make(RegularSlice, len(singularInflections))
	copy(singulars, singularInflections)
	return singulars
}

// GetIrregular retrieves the irregular inflection values
func GetIrregular() IrregularSlice {
	irregular := make(IrregularSlice, len(irregularInflections))
	copy(irregular, irregularInflections)
	return irregular
}

// GetUncountable retrieves the uncountable inflection values
func GetUncountable() []string {
	uncountables := make([]string, len(uncountableInflections))
	copy(uncountables, uncountableInflections)
	return uncountables
}

// SetPlural sets the plural inflections slice
func SetPlural(inflections RegularSlice) {
	pluralInflections = inflections
	compile()
}

// SetSingular sets the singular inflections slice
func SetSingular(inflections RegularSlice) {
	singularInflections = inflections
	compile()
}

// SetIrregular sets the irregular inflections slice
func SetIrregular(inflections IrregularSlice) {
	irregularInflections = inflections
	compile()
}

// SetUncountable sets the uncountable inflections slice
func SetUncountable(inflections []string) {
	uncountableInflections = inflections
	compile()
}

// Plural converts a word to its plural form
func Plural(str string) string {
	for _, inflection := range compiledPluralMaps {
		if inflection.regexp.MatchString(str) {
			return inflection.regexp.ReplaceAllString(str, inflection.replace)
		}
	}
	return str
}

// Singular converts a word to its singular form
func Singular(str string) string {
	for _, inflection := range compiledSingularMaps {
		if inflection.regexp.MatchString(str) {
			return inflection.regexp.ReplaceAllString(str, inflection.replace)
		}
	}
	return str
}
<commit_msg>Add evidence to uncountable<commit_after>/*
Package inflection pluralizes and singularizes English nouns.

		inflection.Plural("person") => "people"
		inflection.Plural("Person") => "People"
		inflection.Plural("PERSON") => "PEOPLE"

		inflection.Singular("people") => "person"
		inflection.Singular("People") => "Person"
		inflection.Singular("PEOPLE") => "PERSON"

		inflection.Plural("FancyPerson") => "FancydPeople"
		inflection.Singular("FancyPeople") => "FancydPerson"

Standard rules are from Rails's ActiveSupport (https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb)

If you want to register more rules, follow:

		inflection.AddUncountable("fish")
		inflection.AddIrregular("person", "people")
		inflection.AddPlural("(bu)s$", "${1}ses") # "bus" => "buses" / "BUS" => "BUSES" / "Bus" => "Buses"
		inflection.AddSingular("(bus)(es)?$", "${1}") # "buses" => "bus" / "Buses" => "Bus" / "BUSES" => "BUS"
*/
package inflection

import (
	"regexp"
	"strings"
)

type inflection struct {
	regexp  *regexp.Regexp
	replace string
}

// Regular is a regexp find replace inflection
type Regular struct {
	find    string
	replace string
}

// Irregular is a hard replace inflection,
// containing both singular and plural forms
type Irregular struct {
	singular string
	plural   string
}

// RegularSlice is a slice of Regular inflections
type RegularSlice []Regular

// IrregularSlice is a slice of Irregular inflections
type IrregularSlice []Irregular

var pluralInflections = RegularSlice{
	{"([a-z])$", "${1}s"},
	{"s$", "s"},
	{"^(ax|test)is$", "${1}es"},
	{"(octop|vir)us$", "${1}i"},
	{"(octop|vir)i$", "${1}i"},
	{"(alias|status)$", "${1}es"},
	{"(bu)s$", "${1}ses"},
	{"(buffal|tomat)o$", "${1}oes"},
	{"([ti])um$", "${1}a"},
	{"([ti])a$", "${1}a"},
	{"sis$", "ses"},
	{"(?:([^f])fe|([lr])f)$", "${1}${2}ves"},
	{"(hive)$", "${1}s"},
	{"([^aeiouy]|qu)y$", "${1}ies"},
	{"(x|ch|ss|sh)$", "${1}es"},
	{"(matr|vert|ind)(?:ix|ex)$", "${1}ices"},
	{"^(m|l)ouse$", "${1}ice"},
	{"^(m|l)ice$", "${1}ice"},
	{"^(ox)$", "${1}en"},
	{"^(oxen)$", "${1}"},
	{"(quiz)$", "${1}zes"},
}

var singularInflections = RegularSlice{
	{"s$", ""},
	{"(ss)$", "${1}"},
	{"(n)ews$", "${1}ews"},
	{"([ti])a$", "${1}um"},
	{"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$", "${1}sis"},
	{"(^analy)(sis|ses)$", "${1}sis"},
	{"([^f])ves$", "${1}fe"},
	{"(hive)s$", "${1}"},
	{"(tive)s$", "${1}"},
	{"([lr])ves$", "${1}f"},
	{"([^aeiouy]|qu)ies$", "${1}y"},
	{"(s)eries$", "${1}eries"},
	{"(m)ovies$", "${1}ovie"},
	{"(c)ookies$", "${1}ookie"},
	{"(x|ch|ss|sh)es$", "${1}"},
	{"^(m|l)ice$", "${1}ouse"},
	{"(bus)(es)?$", "${1}"},
	{"(o)es$", "${1}"},
	{"(shoe)s$", "${1}"},
	{"(cris|test)(is|es)$", "${1}is"},
	{"^(a)x[ie]s$", "${1}xis"},
	{"(octop|vir)(us|i)$", "${1}us"},
	{"(alias|status)(es)?$", "${1}"},
	{"^(ox)en", "${1}"},
	{"(vert|ind)ices$", "${1}ex"},
	{"(matr)ices$", "${1}ix"},
	{"(quiz)zes$", "${1}"},
	{"(database)s$", "${1}"},
}

var irregularInflections = IrregularSlice{
	{"person", "people"},
	{"man", "men"},
	{"child", "children"},
	{"sex", "sexes"},
	{"move", "moves"},
	{"mombie", "mombies"},
}

var uncountableInflections = []string{"equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "jeans", "police", "evidence"}

var compiledPluralMaps []inflection
var compiledSingularMaps []inflection

func compile() {
	compiledPluralMaps = []inflection{}
	compiledSingularMaps = []inflection{}
	for _, uncountable := range uncountableInflections {
		inf := inflection{
			regexp:  regexp.MustCompile("^(?i)(" + uncountable + ")$"),
			replace: "${1}",
		}
		compiledPluralMaps = append(compiledPluralMaps, inf)
		compiledSingularMaps = append(compiledSingularMaps, inf)
	}

	for _, value := range irregularInflections {
		infs := []inflection{
			inflection{regexp: regexp.MustCompile(strings.ToUpper(value.singular) + "$"), replace: strings.ToUpper(value.plural)},
			inflection{regexp: regexp.MustCompile(strings.Title(value.singular) + "$"), replace: strings.Title(value.plural)},
			inflection{regexp: regexp.MustCompile(value.singular + "$"), replace: value.plural},
		}
		compiledPluralMaps = append(compiledPluralMaps, infs...)
	}

	for _, value := range irregularInflections {
		infs := []inflection{
			inflection{regexp: regexp.MustCompile(strings.ToUpper(value.plural) + "$"), replace: strings.ToUpper(value.singular)},
			inflection{regexp: regexp.MustCompile(strings.Title(value.plural) + "$"), replace: strings.Title(value.singular)},
			inflection{regexp: regexp.MustCompile(value.plural + "$"), replace: value.singular},
		}
		compiledSingularMaps = append(compiledSingularMaps, infs...)
	}

	for i := len(pluralInflections) - 1; i >= 0; i-- {
		value := pluralInflections[i]
		infs := []inflection{
			inflection{regexp: regexp.MustCompile(strings.ToUpper(value.find)), replace: strings.ToUpper(value.replace)},
			inflection{regexp: regexp.MustCompile(value.find), replace: value.replace},
			inflection{regexp: regexp.MustCompile("(?i)" + value.find), replace: value.replace},
		}
		compiledPluralMaps = append(compiledPluralMaps, infs...)
	}

	for i := len(singularInflections) - 1; i >= 0; i-- {
		value := singularInflections[i]
		infs := []inflection{
			inflection{regexp: regexp.MustCompile(strings.ToUpper(value.find)), replace: strings.ToUpper(value.replace)},
			inflection{regexp: regexp.MustCompile(value.find), replace: value.replace},
			inflection{regexp: regexp.MustCompile("(?i)" + value.find), replace: value.replace},
		}
		compiledSingularMaps = append(compiledSingularMaps, infs...)
	}
}

func init() {
	compile()
}

// AddPlural adds a plural inflection
func AddPlural(find, replace string) {
	pluralInflections = append(pluralInflections, Regular{find, replace})
	compile()
}

// AddSingular adds a singular inflection
func AddSingular(find, replace string) {
	singularInflections = append(singularInflections, Regular{find, replace})
	compile()
}

// AddIrregular adds an irregular inflection
func AddIrregular(singular, plural string) {
	irregularInflections = append(irregularInflections, Irregular{singular, plural})
	compile()
}

// AddUncountable adds an uncountable inflection
func AddUncountable(values ...string) {
	uncountableInflections = append(uncountableInflections, values...)
	compile()
}

// GetPlural retrieves the plural inflection values
func GetPlural() RegularSlice {
	plurals := make(RegularSlice, len(pluralInflections))
	copy(plurals, pluralInflections)
	return plurals
}

// GetSingular retrieves the singular inflection values
func GetSingular() RegularSlice {
	singulars := make(RegularSlice, len(singularInflections))
	copy(singulars, singularInflections)
	return singulars
}

// GetIrregular retrieves the irregular inflection values
func GetIrregular() IrregularSlice {
	irregular := make(IrregularSlice, len(irregularInflections))
	copy(irregular, irregularInflections)
	return irregular
}

// GetUncountable retrieves the uncountable inflection values
func GetUncountable() []string {
	uncountables := make([]string, len(uncountableInflections))
	copy(uncountables, uncountableInflections)
	return uncountables
}

// SetPlural sets the plural inflections slice
func SetPlural(inflections RegularSlice) {
	pluralInflections = inflections
	compile()
}

// SetSingular sets the singular inflections slice
func SetSingular(inflections RegularSlice) {
	singularInflections = inflections
	compile()
}

// SetIrregular sets the irregular inflections slice
func SetIrregular(inflections IrregularSlice) {
	irregularInflections = inflections
	compile()
}

// SetUncountable sets the uncountable inflections slice
func SetUncountable(inflections []string) {
	uncountableInflections = inflections
	compile()
}

// Plural converts a word to its plural form
func Plural(str string) string {
	for _, inflection := range compiledPluralMaps {
		if inflection.regexp.MatchString(str) {
			return inflection.regexp.ReplaceAllString(str, inflection.replace)
		}
	}
	return str
}

// Singular converts a word to its singular form
func Singular(str string) string {
	for _, inflection := range compiledSingularMaps {
		if inflection.regexp.MatchString(str) {
			return inflection.regexp.ReplaceAllString(str, inflection.replace)
		}
	}
	return str
}
<|endoftext|>


<commit_before>package sshmux

import (
	"errors"
	"fmt"
	"io"
	"strconv"
)

// DefaultInteractive is the default server selection prompt for users during
// session forward.
func DefaultInteractive(comm io.ReadWriter, session *Session) (string, error) {
	remotes := session.Remotes

	fmt.Fprintf(comm, "Welcome to sshmux, %s\r\n", session.Conn.User())
	for i, v := range remotes {
		fmt.Fprintf(comm, "    [%d] %s\r\n", i, v)
	}

	// Beware, nasty comm parsing loop
loop:
	for {
		fmt.Fprintf(comm, "Please select remote server: ")
		var buf []byte
		b := make([]byte, 1)
		var (
			n   int
			err error
		)
		for {
			if err != nil {
				return "", err
			}
			n, err = comm.Read(b)
			if n == 1 {
				fmt.Fprintf(comm, "%s", b)
				switch b[0] {
				case '\r':
					fmt.Fprintf(comm, "\r\n")
					res, err := strconv.ParseInt(string(buf), 10, 64)
					if err != nil {
						fmt.Fprintf(comm, "comm not a valid integer. Please try again\r\n")
						continue loop
					}
					if int(res) >= len(remotes) || res < 0 {
						fmt.Fprintf(comm, "No such server. Please try again\r\n")
						continue loop
					}

					return remotes[int(res)], nil
				case 0x03:
					fmt.Fprintf(comm, "\r\nGoodbye\r\n")
					return "", errors.New("user terminated session")
				}

				buf = append(buf, b[0])
			}
		}
	}
}
<commit_msg>Fix a few strings<commit_after>package sshmux

import (
	"errors"
	"fmt"
	"io"
	"strconv"
)

// DefaultInteractive is the default server selection prompt for users during
// session forward.
func DefaultInteractive(comm io.ReadWriter, session *Session) (string, error) {
	remotes := session.Remotes

	fmt.Fprintf(comm, "Welcome to sshmux, %s\r\n", session.Conn.User())
	for i, v := range remotes {
		fmt.Fprintf(comm, "    [%d] %s\r\n", i, v)
	}

	// Beware, nasty input parsing loop
loop:
	for {
		fmt.Fprintf(comm, "Please select remote server: ")
		var buf []byte
		b := make([]byte, 1)
		var (
			n   int
			err error
		)
		for {
			if err != nil {
				return "", err
			}
			n, err = comm.Read(b)
			if n == 1 {
				fmt.Fprintf(comm, "%s", b)
				switch b[0] {
				case '\r':
					fmt.Fprintf(comm, "\r\n")
					res, err := strconv.ParseInt(string(buf), 10, 64)
					if err != nil {
						fmt.Fprintf(comm, "input not a valid integer. Please try again\r\n")
						continue loop
					}
					if int(res) >= len(remotes) || res < 0 {
						fmt.Fprintf(comm, "No such server. Please try again\r\n")
						continue loop
					}

					return remotes[int(res)], nil
				case 0x03:
					fmt.Fprintf(comm, "\r\nGoodbye\r\n")
					return "", errors.New("user terminated session")
				}

				buf = append(buf, b[0])
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"

	"gopkg.in/bsm/openrtb.v1"
)

const initialCapacity = 25 // No special reason why it's 25.

type Creative struct {
	Format string `json:"format"`
	Id     int    `json:"id"`
	Name   string `json:"name"`
}

// This is the agent configuration that will be sent to RTBKIT's ACS
type AgentConfig struct {
	// We use `RawMessage` for Augmentations and BidcControl, because we
	// don't need it, we just cache it.
	Account            []string         `json:"account"`
	Augmentations      *json.RawMessage `json:"augmentations"`
	BidControl         *json.RawMessage `json:"bidControl"`
	BidProbability     float64          `json:"bidProbability"`
	Creatives          []Creative       `json:"creatives"`
	ErrorFormat        string           `json:"errorFormat"`
	External           bool             `json:"external"`
	ExternalId         int              `json:"externalId"`
	LossFormat         string           `json:"lossFormat"`
	MinTimeAvailableMs float64          `json:"minTimeAvailableMs"`
	WinFormat          string           `json:"winFormat"`
}

// This represents a RTBKIT Agent
type Agent struct {
	Name   string      `json:"name"`
	Config AgentConfig `json:"config"`

	// This is the price the agent will pay per impression. "Fixed price bidder".
	Price float64 `json:"price"`

	// For pacing the budgeting
	Period  int `json:"period"`
	Balance int `json:"balance"`

	// private state of each agent
	registered bool      // did we register the configuration in the ACS?
	pacer      chan bool // go routine updating balance in the banker
	bidId      int       // unique id for response
}

type creativesKey struct {
	// This is used to make a mapping between an impression and the
	// external-id of an agent to the creatives that can be sent to the
	// exchange for that impression.
	ImpId string
	ExtId int
}

// Register Agent in the ACS sending a HTTP request to the service on `acsIp`:`acsPort`
func (agent *Agent) RegisterAgent(
	httpClient *http.Client, acsIp string, acsPort int) {
	url := fmt.Sprintf("http://%s:%d/v1/agents/%s/config", acsIp, acsPort, agent.Name)
	body, _ := json.Marshal(agent.Config)
	reader := bytes.NewReader(body)
	req, _ := http.NewRequest("POST", url, reader)
	req.Header.Add("Accept", "application/json")
	res, err := httpClient.Do(req)
	if err != nil {
		log.Printf("ACS registration failed with %s\n", err)
		return
	}
	agent.registered = true
	res.Body.Close()
}

// Removes the agent configuration from the ACS
func (agent *Agent) UnregisterAgent(
	httpClient *http.Client, acsIp string, acsPort int) {
	url := fmt.Sprintf("http://%s:%d/v1/agents/%s/config", acsIp, acsPort, agent.Name)
	req, _ := http.NewRequest("DELETE", url, bytes.NewBufferString(""))
	res, err := httpClient.Do(req)
	if err != nil {
		log.Printf("Unregister failed with %s\n", err)
		return
	}
	agent.registered = false
	res.Body.Close()
}

// Starts a go routine which periodically updates the balance on the agents account.
func (agent *Agent) StartPacer(
	httpClient *http.Client, bankerIp string, bankerPort int) {

	accounts := agent.Config.Account

	url := fmt.Sprintf("http://%s:%d/v1/accounts/%s/balance",
		bankerIp, bankerPort, strings.Join(accounts, ":"))
	body := fmt.Sprintf("{\"USD/1M\": %d}", agent.Balance)
	ticker := time.NewTicker(time.Duration(agent.Period) * time.Millisecond)
	agent.pacer = make(chan bool)

	go func() {
		for {
			select {
			case <-ticker.C:
				// make this a new go routine?
				go func() {
					log.Println("Pacing...")
					req, _ := http.NewRequest("POST", url, strings.NewReader(body))
					req.Header.Add("Accept", "application/json")
					res, err := httpClient.Do(req)
					if err != nil {
						log.Printf("Balance failed with %s\n", err)
						return
					}
					res.Body.Close()
				}()
			case <-agent.pacer:
				ticker.Stop()
				return
			}
		}
	}()
}

// Stops the go routine updating the bank balance.
func (agent *Agent) StopPacer() {
	close(agent.pacer)
}

// Adds to the bid response the bid by the agent. The Bid is added to
// the only seat of the response. It picks a random creative from
// the list of creatives from the `Agent.Config.Creative` and places it
// in the bid.
func (agent *Agent) DoBid(
	req *openrtb.Request, res *openrtb.Response, ids map[creativesKey]interface{}) (*openrtb.Response, bool) {

	for _, imp := range req.Imp {
		key := creativesKey{ImpId: *imp.Id, ExtId: agent.Config.ExternalId}
		if ids[key] == nil {
			continue
		}
		creativeList := ids[key].([]interface{})
		// pick a random creative
		n := rand.Intn(len(creativeList))

		// JSON reads numbers as float64...
		cridx := int(creativeList[n].(float64))
		// ...but this (`cridx` see below) is an index.
		creative := agent.Config.Creatives[cridx]
		crid := strconv.Itoa(creative.Id)

		// the `bidId` should be something else,
		// it is used for tracking the bid,
		// but we are not tracking anything yet.
		bidId := strconv.Itoa(agent.bidId)

		price := float32(agent.Price)

		ext := map[string]interface{}{"priority": 1.0, "external-id": agent.Config.ExternalId}
		bid := openrtb.Bid{Id: &bidId, Impid: imp.Id, Crid: &crid, Price: &price, Ext: ext}
		agent.bidId += 1
		res.Seatbid[0].Bid = append(res.Seatbid[0].Bid, bid)
	}
	return res, len(res.Seatbid[0].Bid) > 0
}

func externalIdsFromRequest(req *openrtb.Request) map[creativesKey]interface{} {
	// This function makes a mappping with a range of type (Impression Id, External Id)
	// to a slice of "creative indexes" (See the agent configuration "creative").
	// We use this auxiliary function in `DoBid` to match the `BidRequest` to the
	// creatives of the agent and create a response.
	ids := make(map[creativesKey]interface{})

	for _, imp := range req.Imp {
		for _, extId := range imp.Ext["external-ids"].([]interface{}) {
			key := creativesKey{ImpId: *imp.Id, ExtId: int(extId.(float64))}
			creatives := (imp.Ext["creative-indexes"].(map[string]interface{}))[strconv.Itoa(int(extId.(float64)))]
			ids[key] = creatives.(interface{})
		}
	}
	return ids
}

func emptyResponseWithOneSeat(req *openrtb.Request) *openrtb.Response {
	// This function adds a Seat to the Response.
	// Seat: A buyer entity that uses a Bidder to obtain impressions on its behalf.
	seat := openrtb.Seatbid{Bid: make([]openrtb.Bid, 0)}
	seatbid := []openrtb.Seatbid{seat}
	res := &openrtb.Response{Id: req.Id, Seatbid: seatbid}
	return res
}

// Parse a JSON file and return an Agent.
func LoadAgent(filepath string) (Agent, error) {
	var agent Agent
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return Agent{}, err
	}
	err = json.Unmarshal(data, &agent)
	if err != nil {
		return Agent{}, err
	}
	return agent, nil
}

// Parse a JSON file and return a list of Agents.
func LoadAgentsFromFile(filepath string) ([]Agent, error) {
	type Agents []Agent
	var agents Agents

	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return agents, err
	}
	err = json.Unmarshal(data, &agents)
	if err != nil {
		return agents, err
	}
	return agents, nil
}
<commit_msg>Importing openrtb.v2<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"

	openrtb "gopkg.in/bsm/openrtb.v2"
)

const initialCapacity = 25 // No special reason why it's 25.

type Creative struct {
	Format string `json:"format"`
	Id     int    `json:"id"`
	Name   string `json:"name"`
}

// This is the agent configuration that will be sent to RTBKIT's ACS
type AgentConfig struct {
	// We use `RawMessage` for Augmentations and BidcControl, because we
	// don't need it, we just cache it.
	Account            []string         `json:"account"`
	Augmentations      *json.RawMessage `json:"augmentations"`
	BidControl         *json.RawMessage `json:"bidControl"`
	BidProbability     float64          `json:"bidProbability"`
	Creatives          []Creative       `json:"creatives"`
	ErrorFormat        string           `json:"errorFormat"`
	External           bool             `json:"external"`
	ExternalId         int              `json:"externalId"`
	LossFormat         string           `json:"lossFormat"`
	MinTimeAvailableMs float64          `json:"minTimeAvailableMs"`
	WinFormat          string           `json:"winFormat"`
}

// This represents a RTBKIT Agent
type Agent struct {
	Name   string      `json:"name"`
	Config AgentConfig `json:"config"`

	// This is the price the agent will pay per impression. "Fixed price bidder".
	Price float64 `json:"price"`

	// For pacing the budgeting
	Period  int `json:"period"`
	Balance int `json:"balance"`

	// private state of each agent
	registered bool      // did we register the configuration in the ACS?
	pacer      chan bool // go routine updating balance in the banker
	bidId      int       // unique id for response
}

type creativesKey struct {
	// This is used to make a mapping between an impression and the
	// external-id of an agent to the creatives that can be sent to the
	// exchange for that impression.
	ImpId string
	ExtId int
}

// Register Agent in the ACS sending a HTTP request to the service on `acsIp`:`acsPort`
func (agent *Agent) RegisterAgent(
	httpClient *http.Client, acsIp string, acsPort int) {
	url := fmt.Sprintf("http://%s:%d/v1/agents/%s/config", acsIp, acsPort, agent.Name)
	body, _ := json.Marshal(agent.Config)
	reader := bytes.NewReader(body)
	req, _ := http.NewRequest("POST", url, reader)
	req.Header.Add("Accept", "application/json")
	res, err := httpClient.Do(req)
	if err != nil {
		log.Printf("ACS registration failed with %s\n", err)
		return
	}
	agent.registered = true
	res.Body.Close()
}

// Removes the agent configuration from the ACS
func (agent *Agent) UnregisterAgent(
	httpClient *http.Client, acsIp string, acsPort int) {
	url := fmt.Sprintf("http://%s:%d/v1/agents/%s/config", acsIp, acsPort, agent.Name)
	req, _ := http.NewRequest("DELETE", url, bytes.NewBufferString(""))
	res, err := httpClient.Do(req)
	if err != nil {
		log.Printf("Unregister failed with %s\n", err)
		return
	}
	agent.registered = false
	res.Body.Close()
}

// Starts a go routine which periodically updates the balance on the agents account.
func (agent *Agent) StartPacer(
	httpClient *http.Client, bankerIp string, bankerPort int) {

	accounts := agent.Config.Account

	url := fmt.Sprintf("http://%s:%d/v1/accounts/%s/balance",
		bankerIp, bankerPort, strings.Join(accounts, ":"))
	body := fmt.Sprintf("{\"USD/1M\": %d}", agent.Balance)
	ticker := time.NewTicker(time.Duration(agent.Period) * time.Millisecond)
	agent.pacer = make(chan bool)

	go func() {
		for {
			select {
			case <-ticker.C:
				// make this a new go routine?
				go func() {
					log.Println("Pacing...")
					req, _ := http.NewRequest("POST", url, strings.NewReader(body))
					req.Header.Add("Accept", "application/json")
					res, err := httpClient.Do(req)
					if err != nil {
						log.Printf("Balance failed with %s\n", err)
						return
					}
					res.Body.Close()
				}()
			case <-agent.pacer:
				ticker.Stop()
				return
			}
		}
	}()
}

// Stops the go routine updating the bank balance.
func (agent *Agent) StopPacer() {
	close(agent.pacer)
}

// Adds to the bid response the bid by the agent. The Bid is added to
// the only seat of the response. It picks a random creative from
// the list of creatives from the `Agent.Config.Creative` and places it
// in the bid.
func (agent *Agent) DoBid(
	req *openrtb.Request, res *openrtb.Response, ids map[creativesKey]interface{}) (*openrtb.Response, bool) {

	for _, imp := range req.Imp {
		key := creativesKey{ImpId: *imp.Id, ExtId: agent.Config.ExternalId}
		if ids[key] == nil {
			continue
		}
		creativeList := ids[key].([]interface{})
		// pick a random creative
		n := rand.Intn(len(creativeList))

		// JSON reads numbers as float64...
		cridx := int(creativeList[n].(float64))
		// ...but this (`cridx` see below) is an index.
		creative := agent.Config.Creatives[cridx]
		crid := strconv.Itoa(creative.Id)

		// the `bidId` should be something else,
		// it is used for tracking the bid,
		// but we are not tracking anything yet.
		bidId := strconv.Itoa(agent.bidId)

		price := float32(agent.Price)

		ext := map[string]interface{}{"priority": 1.0, "external-id": agent.Config.ExternalId}
		bid := openrtb.Bid{Id: &bidId, Impid: imp.Id, Crid: &crid, Price: &price, Ext: ext}
		agent.bidId += 1
		res.Seatbid[0].Bid = append(res.Seatbid[0].Bid, bid)
	}
	return res, len(res.Seatbid[0].Bid) > 0
}

func externalIdsFromRequest(req *openrtb.Request) map[creativesKey]interface{} {
	// This function makes a mappping with a range of type (Impression Id, External Id)
	// to a slice of "creative indexes" (See the agent configuration "creative").
	// We use this auxiliary function in `DoBid` to match the `BidRequest` to the
	// creatives of the agent and create a response.
	ids := make(map[creativesKey]interface{})

	for _, imp := range req.Imp {
		for _, extId := range imp.Ext["external-ids"].([]interface{}) {
			key := creativesKey{ImpId: *imp.Id, ExtId: int(extId.(float64))}
			creatives := (imp.Ext["creative-indexes"].(map[string]interface{}))[strconv.Itoa(int(extId.(float64)))]
			ids[key] = creatives.(interface{})
		}
	}
	return ids
}

func emptyResponseWithOneSeat(req *openrtb.Request) *openrtb.Response {
	// This function adds a Seat to the Response.
	// Seat: A buyer entity that uses a Bidder to obtain impressions on its behalf.
	seat := openrtb.Seatbid{Bid: make([]openrtb.Bid, 0)}
	seatbid := []openrtb.Seatbid{seat}
	res := &openrtb.Response{Id: req.Id, Seatbid: seatbid}
	return res
}

// Parse a JSON file and return an Agent.
func LoadAgent(filepath string) (Agent, error) {
	var agent Agent
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return Agent{}, err
	}
	err = json.Unmarshal(data, &agent)
	if err != nil {
		return Agent{}, err
	}
	return agent, nil
}

// Parse a JSON file and return a list of Agents.
func LoadAgentsFromFile(filepath string) ([]Agent, error) {
	type Agents []Agent
	var agents Agents

	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return agents, err
	}
	err = json.Unmarshal(data, &agents)
	if err != nil {
		return agents, err
	}
	return agents, nil
}
<|endoftext|>


<commit_before>package commands

import (
	"context"
	"errors"
	"sort"
	"strings"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/types"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
)

var ErrInvalidCommand = errors.New("invalid command")
var ErrInvalidArguments = errors.New("invalid arguments")

type Source struct {
	globals.Contextified
	utils.DebugLabeler

	builtins map[chat1.ConversationBuiltinCommandTyp][]types.ConversationCommand
}

func NewSource(g *globals.Context) *Source {
	s := &Source{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "Commands.Source", false),
	}
	s.makeBuiltins()
	return s
}

const (
	cmdCollapse int = iota
	cmdExpand
	cmdFlip
	cmdGiphy
	cmdHeadline
	cmdHide
	cmdJoin
	cmdLeave
	cmdMe
	cmdMsg
	cmdMute
	cmdShrug
	cmdUnhide
)

func (s *Source) allCommands() (res map[int]types.ConversationCommand) {
	res = make(map[int]types.ConversationCommand)
	res[cmdCollapse] = NewCollapse(s.G())
	res[cmdExpand] = NewExpand(s.G())
	res[cmdFlip] = NewFlip(s.G())
	res[cmdGiphy] = NewGiphy(s.G())
	res[cmdHeadline] = NewHeadline(s.G())
	res[cmdHide] = NewHide(s.G())
	res[cmdJoin] = NewJoin(s.G())
	res[cmdLeave] = NewLeave(s.G())
	res[cmdMe] = NewMe(s.G())
	res[cmdMsg] = NewMsg(s.G())
	res[cmdMute] = NewMute(s.G())
	res[cmdShrug] = NewShrug(s.G())
	res[cmdUnhide] = NewUnhide(s.G())
	return res
}

func (s *Source) makeBuiltins() {
	cmds := s.allCommands()
	common := []types.ConversationCommand{
		cmds[cmdCollapse],
		cmds[cmdExpand],
		cmds[cmdFlip],
		cmds[cmdHide],
		cmds[cmdMe],
		cmds[cmdMsg],
		cmds[cmdMute],
		cmds[cmdShrug],
		cmds[cmdUnhide],
	}
	// Giphy only on for admins for now
	if s.isAdmin() {
		common = append(common, cmds[cmdGiphy])
	}
	s.builtins = make(map[chat1.ConversationBuiltinCommandTyp][]types.ConversationCommand)
	s.builtins[chat1.ConversationBuiltinCommandTyp_ADHOC] = common
	s.builtins[chat1.ConversationBuiltinCommandTyp_BIGTEAM] = append([]types.ConversationCommand{
		cmds[cmdHeadline],
		cmds[cmdJoin],
		cmds[cmdLeave],
	}, common...)
	s.builtins[chat1.ConversationBuiltinCommandTyp_BIGTEAMGENERAL] = append([]types.ConversationCommand{
		cmds[cmdHeadline],
		cmds[cmdJoin],
	}, common...)
	s.builtins[chat1.ConversationBuiltinCommandTyp_SMALLTEAM] = append([]types.ConversationCommand{
		cmds[cmdJoin],
	}, common...)
	for _, cmds := range s.builtins {
		sort.Slice(cmds, func(i, j int) bool {
			return cmds[i].Name() < cmds[j].Name()
		})
	}
}

func (s *Source) GetBuiltins(ctx context.Context) (res []chat1.BuiltinCommandGroup) {
	for typ, cmds := range s.builtins {
		var exportCmds []chat1.ConversationCommand
		for _, cmd := range cmds {
			exportCmds = append(exportCmds, cmd.Export())
		}
		res = append(res, chat1.BuiltinCommandGroup{
			Typ:      typ,
			Commands: exportCmds,
		})
	}
	sort.Slice(res, func(i, j int) bool {
		return res[i].Typ < res[j].Typ
	})
	return res
}

func (s *Source) GetBuiltinCommandType(ctx context.Context, c types.ConversationCommandsSpec) chat1.ConversationBuiltinCommandTyp {
	switch c.GetMembersType() {
	case chat1.ConversationMembersType_TEAM:
		switch c.GetTeamType() {
		case chat1.TeamType_COMPLEX:
			if c.GetTopicName() == globals.DefaultTeamTopic {
				return chat1.ConversationBuiltinCommandTyp_BIGTEAMGENERAL
			}
			return chat1.ConversationBuiltinCommandTyp_BIGTEAM
		default:
			return chat1.ConversationBuiltinCommandTyp_SMALLTEAM
		}
	default:
		return chat1.ConversationBuiltinCommandTyp_ADHOC
	}
}

func (s *Source) ListCommands(ctx context.Context, uid gregor1.UID, conv types.ConversationCommandsSpec) (res chat1.ConversationCommandGroups, err error) {
	defer s.Trace(ctx, func() error { return err }, "ListCommands")()
	return chat1.NewConversationCommandGroupsWithBuiltin(s.GetBuiltinCommandType(ctx, conv)), nil
}

func (s *Source) AttemptBuiltinCommand(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName string, body chat1.MessageBody) (handled bool, err error) {
	defer s.Trace(ctx, func() error { return err }, "AttemptBuiltinCommand")()
	if !body.IsType(chat1.MessageType_TEXT) {
		return false, nil
	}
	text := body.Text().Body
	if !strings.HasPrefix(text, "/") {
		return false, nil
	}
	conv, err := getConvByID(ctx, s.G(), uid, convID)
	if err != nil {
		return false, err
	}
	typ := s.GetBuiltinCommandType(ctx, conv)
	for _, cmd := range s.builtins[typ] {
		if cmd.Match(ctx, text) {
			s.Debug(ctx, "AttemptBuiltinCommand: matched command: %s, executing...", cmd.Name())
			return true, cmd.Execute(ctx, uid, convID, tlfName, text)
		}
	}
	return false, nil
}

func (s *Source) PreviewBuiltinCommand(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, text string) {
	defer s.Trace(ctx, func() error { return nil }, "PreviewBuiltinCommand")()
	conv, err := getConvByID(ctx, s.G(), uid, convID)
	if err != nil {
		return
	}
	typ := s.GetBuiltinCommandType(ctx, conv)
	for _, cmd := range s.builtins[typ] {
		// Run preview on everything as long as it is a slash command
		cmd.Preview(ctx, uid, convID, text)
	}
}

func (s *Source) isAdmin() bool {
	username := s.G().GetEnv().GetUsername().String()
	return admins[username]
}

var admins = map[string]bool{
	"mikem":        true,
	"max":          true,
	"chris":        true,
	"chrisnojima":  true,
	"mlsteele":     true,
	"xgess":        true,
	"karenm":       true,
	"joshblum":     true,
	"cjb":          true,
	"jzila":        true,
	"patrick":      true,
	"modalduality": true,
	"strib":        true,
	"songgao":      true,
	"ayoubd":       true,
	"cecileb":      true,
	"adamjspooner": true,
	"akalin":       true,
	"marcopolo":    true,
	"aimeedavid":   true,
	"jinyang":      true,
	"zapu":         true,
	"jakob223":     true,
	"taruti":       true,
	"pzduniak":     true,
	"zanderz":      true,
	"giphy_tester": true,
}
<commit_msg>turn /giphy on for everyone<commit_after>package commands

import (
	"context"
	"errors"
	"sort"
	"strings"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/types"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
)

var ErrInvalidCommand = errors.New("invalid command")
var ErrInvalidArguments = errors.New("invalid arguments")

type Source struct {
	globals.Contextified
	utils.DebugLabeler

	builtins map[chat1.ConversationBuiltinCommandTyp][]types.ConversationCommand
}

func NewSource(g *globals.Context) *Source {
	s := &Source{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "Commands.Source", false),
	}
	s.makeBuiltins()
	return s
}

const (
	cmdCollapse int = iota
	cmdExpand
	cmdFlip
	cmdGiphy
	cmdHeadline
	cmdHide
	cmdJoin
	cmdLeave
	cmdMe
	cmdMsg
	cmdMute
	cmdShrug
	cmdUnhide
)

func (s *Source) allCommands() (res map[int]types.ConversationCommand) {
	res = make(map[int]types.ConversationCommand)
	res[cmdCollapse] = NewCollapse(s.G())
	res[cmdExpand] = NewExpand(s.G())
	res[cmdFlip] = NewFlip(s.G())
	res[cmdGiphy] = NewGiphy(s.G())
	res[cmdHeadline] = NewHeadline(s.G())
	res[cmdHide] = NewHide(s.G())
	res[cmdJoin] = NewJoin(s.G())
	res[cmdLeave] = NewLeave(s.G())
	res[cmdMe] = NewMe(s.G())
	res[cmdMsg] = NewMsg(s.G())
	res[cmdMute] = NewMute(s.G())
	res[cmdShrug] = NewShrug(s.G())
	res[cmdUnhide] = NewUnhide(s.G())
	return res
}

func (s *Source) makeBuiltins() {
	cmds := s.allCommands()
	common := []types.ConversationCommand{
		cmds[cmdCollapse],
		cmds[cmdExpand],
		cmds[cmdFlip],
		cmds[cmdGiphy],
		cmds[cmdHide],
		cmds[cmdMe],
		cmds[cmdMsg],
		cmds[cmdMute],
		cmds[cmdShrug],
		cmds[cmdUnhide],
	}
	s.builtins = make(map[chat1.ConversationBuiltinCommandTyp][]types.ConversationCommand)
	s.builtins[chat1.ConversationBuiltinCommandTyp_ADHOC] = common
	s.builtins[chat1.ConversationBuiltinCommandTyp_BIGTEAM] = append([]types.ConversationCommand{
		cmds[cmdHeadline],
		cmds[cmdJoin],
		cmds[cmdLeave],
	}, common...)
	s.builtins[chat1.ConversationBuiltinCommandTyp_BIGTEAMGENERAL] = append([]types.ConversationCommand{
		cmds[cmdHeadline],
		cmds[cmdJoin],
	}, common...)
	s.builtins[chat1.ConversationBuiltinCommandTyp_SMALLTEAM] = append([]types.ConversationCommand{
		cmds[cmdJoin],
	}, common...)
	for _, cmds := range s.builtins {
		sort.Slice(cmds, func(i, j int) bool {
			return cmds[i].Name() < cmds[j].Name()
		})
	}
}

func (s *Source) GetBuiltins(ctx context.Context) (res []chat1.BuiltinCommandGroup) {
	for typ, cmds := range s.builtins {
		var exportCmds []chat1.ConversationCommand
		for _, cmd := range cmds {
			exportCmds = append(exportCmds, cmd.Export())
		}
		res = append(res, chat1.BuiltinCommandGroup{
			Typ:      typ,
			Commands: exportCmds,
		})
	}
	sort.Slice(res, func(i, j int) bool {
		return res[i].Typ < res[j].Typ
	})
	return res
}

func (s *Source) GetBuiltinCommandType(ctx context.Context, c types.ConversationCommandsSpec) chat1.ConversationBuiltinCommandTyp {
	switch c.GetMembersType() {
	case chat1.ConversationMembersType_TEAM:
		switch c.GetTeamType() {
		case chat1.TeamType_COMPLEX:
			if c.GetTopicName() == globals.DefaultTeamTopic {
				return chat1.ConversationBuiltinCommandTyp_BIGTEAMGENERAL
			}
			return chat1.ConversationBuiltinCommandTyp_BIGTEAM
		default:
			return chat1.ConversationBuiltinCommandTyp_SMALLTEAM
		}
	default:
		return chat1.ConversationBuiltinCommandTyp_ADHOC
	}
}

func (s *Source) ListCommands(ctx context.Context, uid gregor1.UID, conv types.ConversationCommandsSpec) (res chat1.ConversationCommandGroups, err error) {
	defer s.Trace(ctx, func() error { return err }, "ListCommands")()
	return chat1.NewConversationCommandGroupsWithBuiltin(s.GetBuiltinCommandType(ctx, conv)), nil
}

func (s *Source) AttemptBuiltinCommand(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName string, body chat1.MessageBody) (handled bool, err error) {
	defer s.Trace(ctx, func() error { return err }, "AttemptBuiltinCommand")()
	if !body.IsType(chat1.MessageType_TEXT) {
		return false, nil
	}
	text := body.Text().Body
	if !strings.HasPrefix(text, "/") {
		return false, nil
	}
	conv, err := getConvByID(ctx, s.G(), uid, convID)
	if err != nil {
		return false, err
	}
	typ := s.GetBuiltinCommandType(ctx, conv)
	for _, cmd := range s.builtins[typ] {
		if cmd.Match(ctx, text) {
			s.Debug(ctx, "AttemptBuiltinCommand: matched command: %s, executing...", cmd.Name())
			return true, cmd.Execute(ctx, uid, convID, tlfName, text)
		}
	}
	return false, nil
}

func (s *Source) PreviewBuiltinCommand(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, text string) {
	defer s.Trace(ctx, func() error { return nil }, "PreviewBuiltinCommand")()
	conv, err := getConvByID(ctx, s.G(), uid, convID)
	if err != nil {
		return
	}
	typ := s.GetBuiltinCommandType(ctx, conv)
	for _, cmd := range s.builtins[typ] {
		// Run preview on everything as long as it is a slash command
		cmd.Preview(ctx, uid, convID, text)
	}
}

func (s *Source) isAdmin() bool {
	username := s.G().GetEnv().GetUsername().String()
	return admins[username]
}

var admins = map[string]bool{
	"mikem":        true,
	"max":          true,
	"chris":        true,
	"chrisnojima":  true,
	"mlsteele":     true,
	"xgess":        true,
	"karenm":       true,
	"joshblum":     true,
	"cjb":          true,
	"jzila":        true,
	"patrick":      true,
	"modalduality": true,
	"strib":        true,
	"songgao":      true,
	"ayoubd":       true,
	"cecileb":      true,
	"adamjspooner": true,
	"akalin":       true,
	"marcopolo":    true,
	"aimeedavid":   true,
	"jinyang":      true,
	"zapu":         true,
	"jakob223":     true,
	"taruti":       true,
	"pzduniak":     true,
	"zanderz":      true,
	"giphy_tester": true,
}
<|endoftext|>


<commit_before>package gocd

import (
	"context"
	"fmt"
	"net/url"
)

type AgentsService service

//go:generate gocd-response-links -type=AgentsLinks,AgentLinks
type AgentsLinks struct {
	Self *url.URL
	Doc  *url.URL
}

type AgentLinks struct {
	Self *url.URL
	Doc  *url.URL
	Find *url.URL
}

type AgentsResponse struct {
	Links    AgentsLinks `json:"_links,omitempty"`
	Embedded struct {
		Agents []*Agent `json:"agents"`
	} `json:"_embedded"`
}

type Agent struct {
	Uuid             string        `json:"uuid"`
	Hostname         string        `json:"hostname"`
	ElasticAgentId   string        `json:"elastic_agent_id"`
	ElasticPluginId  string        `json:"elastic_plugin_id"`
	IpAddress        string        `json:"ip_address"`
	Sandbox          string        `json:"sandbox"`
	OperatingSystem  string        `json:"operating_system"`
	FreeSpace        int64         `json:"free_space"`
	AgentConfigState string        `json:"agent_config_state"`
	AgentState       string        `json:"agent_state"`
	Resources        []string      `json:"resources"`
	Environments     []string      `json:"environments"`
	BuildState       string        `json:"build_state"`
	BuildDetails     *BuildDetails `json:"build_details"`
	Links            *AgentLinks   `json:"_links"`
}

type BuildDetails struct {
	Links    *BuildDetailsLinks `json:"_links"`
	Pipeline string             `json:"pipeline"`
	Stage    string             `json:"stage"`
	Job      string             `json:"job"`
}

//go:generate gocd-response-links -type=BuildDetailsLinks
type BuildDetailsLinks struct {
	Job      *url.URL `json:"job"`
	Stage    *url.URL `json:"stage"`
	Pipeline *url.URL `json:"pipeline"`
}

func (s *AgentsService) List(ctx context.Context) ([]*Agent, *APIResponse, error) {
	u, err := addOptions("agents")

	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil, apiV4)
	if err != nil {
		return nil, nil, err
	}

	r := AgentsResponse{}
	resp, err := s.client.Do(ctx, req, &r)
	if err != nil {
		return nil, resp, err
	}

	//return &r.Embedded, resp, nil
	return r.Embedded.Agents, resp, nil
}

func (s *AgentsService) Get(ctx context.Context, uuid string) (*Agent, *APIResponse, error) {
	return s.handleAgentRequest(ctx, "GET", uuid, nil)
}

func (s *AgentsService) Update(ctx context.Context, uuid string, agent *Agent) (*Agent, *APIResponse, error) {
	return s.handleAgentRequest(ctx, "PATCH", uuid, agent)
}

func (s *AgentsService) handleAgentRequest(ctx context.Context, action string, uuid string, body *Agent) (*Agent, *APIResponse, error) {
	u, err := addOptions(fmt.Sprintf("agents/%s", uuid))
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(action, u, body, apiV4)
	if err != nil {
		return nil, nil, err
	}

	a := Agent{}
	resp, err := s.client.Do(ctx, req, &a)
	if err != nil {
		return nil, resp, err
	}

	return &a, resp, nil
}
<commit_msg>Added json paths<commit_after>package gocd

import (
	"context"
	"fmt"
	"net/url"
)

type AgentsService service

//go:generate gocd-response-links -type=AgentsLinks,AgentLinks
type AgentsLinks struct {
	Self *url.URL `json:"self"`
	Doc  *url.URL `json:"doc"`
}

type AgentLinks struct {
	Self *url.URL `json:"self"`
	Doc  *url.URL `json:"doc"`
	Find *url.URL `json:"find"`
}

type AgentsResponse struct {
	Links    AgentsLinks `json:"_links,omitempty"`
	Embedded struct {
		Agents []*Agent `json:"agents"`
	} `json:"_embedded"`
}

type Agent struct {
	Uuid             string        `json:"uuid"`
	Hostname         string        `json:"hostname"`
	ElasticAgentId   string        `json:"elastic_agent_id"`
	ElasticPluginId  string        `json:"elastic_plugin_id"`
	IpAddress        string        `json:"ip_address"`
	Sandbox          string        `json:"sandbox"`
	OperatingSystem  string        `json:"operating_system"`
	FreeSpace        int64         `json:"free_space"`
	AgentConfigState string        `json:"agent_config_state"`
	AgentState       string        `json:"agent_state"`
	Resources        []string      `json:"resources"`
	Environments     []string      `json:"environments"`
	BuildState       string        `json:"build_state"`
	BuildDetails     *BuildDetails `json:"build_details"`
	Links            *AgentLinks   `json:"_links"`
}

type BuildDetails struct {
	Links    *BuildDetailsLinks `json:"_links"`
	Pipeline string             `json:"pipeline"`
	Stage    string             `json:"stage"`
	Job      string             `json:"job"`
}

//go:generate gocd-response-links -type=BuildDetailsLinks
type BuildDetailsLinks struct {
	Job      *url.URL `json:"job"`
	Stage    *url.URL `json:"stage"`
	Pipeline *url.URL `json:"pipeline"`
}

func (s *AgentsService) List(ctx context.Context) ([]*Agent, *APIResponse, error) {
	u, err := addOptions("agents")

	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest("GET", u, nil, apiV4)
	if err != nil {
		return nil, nil, err
	}

	r := AgentsResponse{}
	resp, err := s.client.Do(ctx, req, &r)
	if err != nil {
		return nil, resp, err
	}

	//return &r.Embedded, resp, nil
	return r.Embedded.Agents, resp, nil
}

func (s *AgentsService) Get(ctx context.Context, uuid string) (*Agent, *APIResponse, error) {
	return s.handleAgentRequest(ctx, "GET", uuid, nil)
}

func (s *AgentsService) Update(ctx context.Context, uuid string, agent *Agent) (*Agent, *APIResponse, error) {
	return s.handleAgentRequest(ctx, "PATCH", uuid, agent)
}

func (s *AgentsService) handleAgentRequest(ctx context.Context, action string, uuid string, body *Agent) (*Agent, *APIResponse, error) {
	u, err := addOptions(fmt.Sprintf("agents/%s", uuid))
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(action, u, body, apiV4)
	if err != nil {
		return nil, nil, err
	}

	a := Agent{}
	resp, err := s.client.Do(ctx, req, &a)
	if err != nil {
		return nil, resp, err
	}

	return &a, resp, nil
}
<|endoftext|>


<commit_before>package spotify

import (
	"errors"
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"time"
)

// SimpleAlbum contains basic data about an album.
type SimpleAlbum struct {
	// The name of the album.
	Name string `json:"name"`
	// The type of the album: one of "album",
	// "single", or "compilation".
	AlbumType string `json:"album_type"`
	// The SpotifyID for the album.
	ID ID `json:"id"`
	// The SpotifyURI for the album.
	URI URI `json:"uri"`
	// The markets in which the album is available,
	// identified using ISO 3166-1 alpha-2 country
	// codes.  Note that al album is considered
	// available in a market when at least 1 of its
	// tracks is available in that market.
	AvailableMarkets []string `json:"available_markets"`
	// A link to the Web API enpoint providing full
	// details of the album.
	Endpoint string `json:"href"`
	// The cover art for the album in various sizes,
	// widest first.
	Images []Image `json:"images"`
	// Known external URLs for this album.
	ExternalURLs map[string]string `json:"external_urls"`
}

// Copyright contains the copyright statement associated with an album.
type Copyright struct {
	// The copyright text for the album.
	Text string `json:"text"`
	// The type of copyright.
	Type string `json:"type"`
}

// FullAlbum provides extra album data in addition to the data provided by SimpleAlbum.
type FullAlbum struct {
	SimpleAlbum
	Artists    []SimpleArtist `json:"artists"`
	Copyrights []Copyright    `json:"copyrights"`
	Genres     []string       `json:"genres"`
	// The popularity of the album, represented as an integer between 0 and 100,
	// with 100 being the most popular.  Popularity of an album is calculated
	// from the popularify of the album's individual tracks.
	Popularity int `json:"popularity"`
	// The date the album was first released.  For example, "1981-12-15".
	// Depending on the ReleaseDatePrecision, it might be shown as
	// "1981" or "1981-12". You can use ReleaseDateTime to convert this
	// to a time.Time value.
	ReleaseDate string `json:"release_date"`
	// The precision with which ReleaseDate value is known: "year", "month", or "day"
	ReleaseDatePrecision string            `json:"release_date_precision"`
	Tracks               SimpleTrackPage   `json:"tracks"`
	ExternalIDs          map[string]string `json:"external_ids"`
}

// SavedAlbum provides info about an album saved to an user's account.
type SavedAlbum struct {
	// The date and time the track was saved, represented as an ISO
	// 8601 UTC timestamp with a zero offset (YYYY-MM-DDTHH:MM:SSZ).
	// You can use the TimestampLayout constant to convert this to
	// a time.Time value.
	AddedAt   string `json:"added_at"`
	FullAlbum `json:"album"`
}

// ReleaseDateTime converts the album's ReleaseDate to a time.TimeValue.
// All of the fields in the result may not be valid.  For example, if
// f.ReleaseDatePrecision is "month", then only the month and year
// (but not the day) of the result are valid.
func (f *FullAlbum) ReleaseDateTime() time.Time {
	if f.ReleaseDatePrecision == "day" {
		result, _ := time.Parse(DateLayout, f.ReleaseDate)
		return result
	}
	if f.ReleaseDatePrecision == "month" {
		ym := strings.Split(f.ReleaseDate, "-")
		year, _ := strconv.Atoi(ym[0])
		month, _ := strconv.Atoi(ym[1])
		return time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
	}
	year, _ := strconv.Atoi(f.ReleaseDate)
	return time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
}

// GetAlbum gets Spotify catalog information for a single album, given its Spotify ID.
func (c *Client) GetAlbum(id ID) (*FullAlbum, error) {
	spotifyURL := fmt.Sprintf("%salbums/%s", c.baseURL, id)

	var a FullAlbum

	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return &a, nil
}

func toStringSlice(ids []ID) []string {
	result := make([]string, len(ids))
	for i, str := range ids {
		result[i] = str.String()
	}
	return result
}

// GetAlbums gets Spotify Catalog information for multiple albums, given their
// Spotify IDs.  It supports up to 20 IDs in a single call.  Albums are returned
// in the order requested.  If an album is not found, that position in the
// result slice will be nil.
func (c *Client) GetAlbums(ids ...ID) ([]*FullAlbum, error) {
	if len(ids) > 20 {
		return nil, errors.New("spotify: exceeded maximum number of albums")
	}
	spotifyURL := fmt.Sprintf("%salbums?ids=%s", c.baseURL, strings.Join(toStringSlice(ids), ","))

	var a struct {
		Albums []*FullAlbum `json:"albums"`
	}

	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return a.Albums, nil
}

// AlbumType represents the type of an album. It can be used to filter
// results when searching for albums.
type AlbumType int

// AlbumType values that can be used to filter which types of albums are
// searched for.  These are flags that can be bitwise OR'd together
// to search for multiple types of albums simultaneously.
const (
	AlbumTypeAlbum       AlbumType = 1 << iota
	AlbumTypeSingle                = 1 << iota
	AlbummTypeAppearsOn            = 1 << iota
	AlbumTypeCompilation           = 1 << iota
)

func (at AlbumType) encode() string {
	types := []string{}
	if at&AlbumTypeAlbum != 0 {
		types = append(types, "album")
	}
	if at&AlbumTypeSingle != 0 {
		types = append(types, "single")
	}
	if at&AlbummTypeAppearsOn != 0 {
		types = append(types, "appears_on")
	}
	if at&AlbumTypeCompilation != 0 {
		types = append(types, "compilation")
	}
	return strings.Join(types, ",")
}

// GetAlbumTracks gets the tracks for a particular album.
// If you only care about the tracks, this call is more efficient
// than GetAlbum.
func (c *Client) GetAlbumTracks(id ID) (*SimpleTrackPage, error) {
	return c.GetAlbumTracksOpt(id, -1, -1)
}

// GetAlbumTracksOpt behaves like GetAlbumTracks, with the exception that it
// allows you to specify extra parameters that limit the number of results returned.
// The maximum number of results to return is specified by limit.
// The offset argument can be used to specify the index of the first track to return.
// It can be used along with limit to reqeust the next set of results.
func (c *Client) GetAlbumTracksOpt(id ID, limit, offset int) (*SimpleTrackPage, error) {
	spotifyURL := fmt.Sprintf("%salbums/%s/tracks", c.baseURL, id)
	v := url.Values{}
	if limit != -1 {
		v.Set("limit", strconv.Itoa(limit))
	}
	if offset != -1 {
		v.Set("offset", strconv.Itoa(offset))
	}
	optional := v.Encode()
	if optional != "" {
		spotifyURL = spotifyURL + "?" + optional
	}

	var result SimpleTrackPage
	err := c.get(spotifyURL, &result)
	if err != nil {
		return nil, err
	}

	return &result, nil
}
<commit_msg>SimpleAlbum with SimpleArtists (#58)<commit_after>package spotify

import (
	"errors"
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"time"
)

// SimpleAlbum contains basic data about an album.
type SimpleAlbum struct {
	// The name of the album.
	Name string `json:"name"`
	// A slice of SimpleArtists
	Artists []SimpleArtist `json:"artists"`
	// The type of the album: one of "album",
	// "single", or "compilation".
	AlbumType string `json:"album_type"`
	// The SpotifyID for the album.
	ID ID `json:"id"`
	// The SpotifyURI for the album.
	URI URI `json:"uri"`
	// The markets in which the album is available,
	// identified using ISO 3166-1 alpha-2 country
	// codes.  Note that al album is considered
	// available in a market when at least 1 of its
	// tracks is available in that market.
	AvailableMarkets []string `json:"available_markets"`
	// A link to the Web API enpoint providing full
	// details of the album.
	Endpoint string `json:"href"`
	// The cover art for the album in various sizes,
	// widest first.
	Images []Image `json:"images"`
	// Known external URLs for this album.
	ExternalURLs map[string]string `json:"external_urls"`
}

// Copyright contains the copyright statement associated with an album.
type Copyright struct {
	// The copyright text for the album.
	Text string `json:"text"`
	// The type of copyright.
	Type string `json:"type"`
}

// FullAlbum provides extra album data in addition to the data provided by SimpleAlbum.
type FullAlbum struct {
	SimpleAlbum
	Artists    []SimpleArtist `json:"artists"`
	Copyrights []Copyright    `json:"copyrights"`
	Genres     []string       `json:"genres"`
	// The popularity of the album, represented as an integer between 0 and 100,
	// with 100 being the most popular.  Popularity of an album is calculated
	// from the popularify of the album's individual tracks.
	Popularity int `json:"popularity"`
	// The date the album was first released.  For example, "1981-12-15".
	// Depending on the ReleaseDatePrecision, it might be shown as
	// "1981" or "1981-12". You can use ReleaseDateTime to convert this
	// to a time.Time value.
	ReleaseDate string `json:"release_date"`
	// The precision with which ReleaseDate value is known: "year", "month", or "day"
	ReleaseDatePrecision string            `json:"release_date_precision"`
	Tracks               SimpleTrackPage   `json:"tracks"`
	ExternalIDs          map[string]string `json:"external_ids"`
}

// SavedAlbum provides info about an album saved to an user's account.
type SavedAlbum struct {
	// The date and time the track was saved, represented as an ISO
	// 8601 UTC timestamp with a zero offset (YYYY-MM-DDTHH:MM:SSZ).
	// You can use the TimestampLayout constant to convert this to
	// a time.Time value.
	AddedAt   string `json:"added_at"`
	FullAlbum `json:"album"`
}

// ReleaseDateTime converts the album's ReleaseDate to a time.TimeValue.
// All of the fields in the result may not be valid.  For example, if
// f.ReleaseDatePrecision is "month", then only the month and year
// (but not the day) of the result are valid.
func (f *FullAlbum) ReleaseDateTime() time.Time {
	if f.ReleaseDatePrecision == "day" {
		result, _ := time.Parse(DateLayout, f.ReleaseDate)
		return result
	}
	if f.ReleaseDatePrecision == "month" {
		ym := strings.Split(f.ReleaseDate, "-")
		year, _ := strconv.Atoi(ym[0])
		month, _ := strconv.Atoi(ym[1])
		return time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
	}
	year, _ := strconv.Atoi(f.ReleaseDate)
	return time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
}

// GetAlbum gets Spotify catalog information for a single album, given its Spotify ID.
func (c *Client) GetAlbum(id ID) (*FullAlbum, error) {
	spotifyURL := fmt.Sprintf("%salbums/%s", c.baseURL, id)

	var a FullAlbum

	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return &a, nil
}

func toStringSlice(ids []ID) []string {
	result := make([]string, len(ids))
	for i, str := range ids {
		result[i] = str.String()
	}
	return result
}

// GetAlbums gets Spotify Catalog information for multiple albums, given their
// Spotify IDs.  It supports up to 20 IDs in a single call.  Albums are returned
// in the order requested.  If an album is not found, that position in the
// result slice will be nil.
func (c *Client) GetAlbums(ids ...ID) ([]*FullAlbum, error) {
	if len(ids) > 20 {
		return nil, errors.New("spotify: exceeded maximum number of albums")
	}
	spotifyURL := fmt.Sprintf("%salbums?ids=%s", c.baseURL, strings.Join(toStringSlice(ids), ","))

	var a struct {
		Albums []*FullAlbum `json:"albums"`
	}

	err := c.get(spotifyURL, &a)
	if err != nil {
		return nil, err
	}

	return a.Albums, nil
}

// AlbumType represents the type of an album. It can be used to filter
// results when searching for albums.
type AlbumType int

// AlbumType values that can be used to filter which types of albums are
// searched for.  These are flags that can be bitwise OR'd together
// to search for multiple types of albums simultaneously.
const (
	AlbumTypeAlbum       AlbumType = 1 << iota
	AlbumTypeSingle                = 1 << iota
	AlbummTypeAppearsOn            = 1 << iota
	AlbumTypeCompilation           = 1 << iota
)

func (at AlbumType) encode() string {
	types := []string{}
	if at&AlbumTypeAlbum != 0 {
		types = append(types, "album")
	}
	if at&AlbumTypeSingle != 0 {
		types = append(types, "single")
	}
	if at&AlbummTypeAppearsOn != 0 {
		types = append(types, "appears_on")
	}
	if at&AlbumTypeCompilation != 0 {
		types = append(types, "compilation")
	}
	return strings.Join(types, ",")
}

// GetAlbumTracks gets the tracks for a particular album.
// If you only care about the tracks, this call is more efficient
// than GetAlbum.
func (c *Client) GetAlbumTracks(id ID) (*SimpleTrackPage, error) {
	return c.GetAlbumTracksOpt(id, -1, -1)
}

// GetAlbumTracksOpt behaves like GetAlbumTracks, with the exception that it
// allows you to specify extra parameters that limit the number of results returned.
// The maximum number of results to return is specified by limit.
// The offset argument can be used to specify the index of the first track to return.
// It can be used along with limit to reqeust the next set of results.
func (c *Client) GetAlbumTracksOpt(id ID, limit, offset int) (*SimpleTrackPage, error) {
	spotifyURL := fmt.Sprintf("%salbums/%s/tracks", c.baseURL, id)
	v := url.Values{}
	if limit != -1 {
		v.Set("limit", strconv.Itoa(limit))
	}
	if offset != -1 {
		v.Set("offset", strconv.Itoa(offset))
	}
	optional := v.Encode()
	if optional != "" {
		spotifyURL = spotifyURL + "?" + optional
	}

	var result SimpleTrackPage
	err := c.get(spotifyURL, &result)
	if err != nil {
		return nil, err
	}

	return &result, nil
}
<|endoftext|>


<commit_before>package apns2

// Alert If this property is included, the system displays a standard alert or a banner, based on the user’s setting.
// You can specify a string or a dictionary as the value of alert.
type Alert struct {
	// A short string describing the purpose of the notification.
	// Apple Watch displays this string as part of the notification interface.
	// This string is displayed only briefly and should be crafted so that it can be understood quickly.
	// This key was added in iOS 8.2.
	Title string `json:"title,omitempty"`

	// The text of the alert message.
	Body string `json:"body,omitempty"`

	// The key to a title string in the Localizable.strings file
	// for the current localization. The key string can be formatted
	// with %@ and %n$@ specifiers to take the variables specified in the
	// title-loc-args array. See Localized Formatted Strings for more information.
	// This key was added in iOS 8.2.
	TitleLocKey string `json:"title-loc-key,omitempty"`

	// Variable string values to appear in place of the format specifiers in title-loc-key.
	// See Localized Formatted Strings for more information.
	// This key was added in iOS 8.2.
	TitleLocArgs []string `json:"title-loc-args,omitempty"`

	// If a string is specified, the system displays an alert that includes the Close and View buttons.
	// The string is used as a key to get a localized string in the current localization to use
	// for the right button’s title instead of “View”. See Localized Formatted Strings for more information.
	ActionLocKey string `json:"action-loc-key,omitempty"`

	// A key to an alert-message string in a Localizable.strings file for the current
	// localization (which is set by the user’s language preference).
	// The key string can be formatted with %@ and %n$@ specifiers to take the variables specified in the loc-args array. See Localized Formatted Strings for more information.
	LocKey string `json:"loc-key,omitempty"`

	// Variable string values to appear in place of the format specifiers in loc-key. See Localized Formatted Strings for more information.
	LocArgs []string `json:"loc-args,omitempty"`

	// The filename of an image file in the app bundle; it may include the extension or omit it.
	// The image is used as the launch image when users tap the action button or move the action slider.
	// If this property is not specified, the system either uses the previous snapshot,uses the image
	// identified by the UILaunchImageFile key in the app’s Info.plist file, or falls back to Default.png.
	// This property was added in iOS 4.0.
	LaunchImage string `json:"launch-image,omitempty"`
}

// “The following payload has an aps dictionary with a simple,
// recommended form for alert messages with the default alert buttons (Close and View).
// It uses a string as the value of alert rather than a dictionary.
// This payload also has a custom array property.
/*
{
    "aps" : { "alert" : "Message received from Bob" },
    "acme2" : [ "bang",  "whiz" ]
}
*/
func (a *Alert) isSimpleForm() bool {
	return len(a.Title) == 0 && len(a.TitleLocKey) == 0 && len(a.TitleLocArgs) == 0 && len(a.ActionLocKey) == 0 && len(a.LocKey) == 0 && len(a.LocArgs) == 0 && len(a.LaunchImage) == 0
}

// Returns bool if alert is valid
func (a *Alert) isValid() bool {
	return a.isSimpleForm() && len(a.Body) == 0
}
<commit_msg>refactor<commit_after>package apns2

// Alert If this property is included, the system displays a standard alert or a banner, based
// on the user’s setting. You can specify a string or a dictionary as the value of alert.
type Alert struct {
	// A short string describing the purpose of the notification. Apple Watch
	// displays this string as part of the notification interface. This
	// string is displayed only briefly and should be crafted so that
	// it can be understood quickly. This key was added in iOS 8.2.
	Title string `json:"title,omitempty"`

	// The text of the alert message.
	Body string `json:"body,omitempty"`

	// The key to a title string in the Localizable.strings file for the current localization.
	// The key string can be formatted with %@ and %n$@ specifiers to take the variables
	// specified in the title-loc-args array. See Localized Formatted Strings for
	// more information.This key was added in iOS 8.2.
	TitleLocKey string `json:"title-loc-key,omitempty"`

	// Variable string values to appear in place of the format specifiers in title-loc-key.
	// See Localized Formatted Strings for more information.This key was added in iOS 8.2.
	TitleLocArgs []string `json:"title-loc-args,omitempty"`

	// If a string is specified, the system displays an alert that includes the Close and View buttons.
	// The string is used as a key to get a localized string in the current localization to use
	// for the right button’s title instead of “View”. See Localized Formatted Strings
	// for more information.
	ActionLocKey string `json:"action-loc-key,omitempty"`

	// A key to an alert-message string in a Localizable.strings file for the current
	// localization (which is set by the user’s language preference).The key string
	// can be formatted with %@ and %n$@ specifiers to take the variables
	// specified in the loc-args array. See Localized Formatted
	// Strings for more information.
	LocKey string `json:"loc-key,omitempty"`

	// Variable string values to appear in place of the format specifiers in loc-key. See Localized Formatted Strings for more information.
	LocArgs []string `json:"loc-args,omitempty"`

	// The filename of an image file in the app bundle; it may include the extension or omit it.
	// The image is used as the launch image when users tap the action button or move the
	// action slider.If this property is not specified, the system either uses the
	// previous snapshot,uses the image identified by the UILaunchImageFile key
	// in the app’s Info.plist file, or falls back to Default.png.
	// This property was added in iOS 4.0.
	LaunchImage string `json:"launch-image,omitempty"`
}

// “The following payload has an aps dictionary with a simple, recommended form
// for alert messages with the default alert buttons (Close and View).
// It uses a string as the value of alert rather than a
// dictionary. This payload also has a custom array property.
/*
{
    "aps" : { "alert" : "Message received from Bob" },
    "acme2" : [ "bang",  "whiz" ]
}
*/
func (a *Alert) isSimpleForm() bool {
	return len(a.Title) == 0 && len(a.TitleLocKey) == 0 && len(a.TitleLocArgs) == 0 && len(a.ActionLocKey) == 0 && len(a.LocKey) == 0 && len(a.LocArgs) == 0 && len(a.LaunchImage) == 0
}

// Returns bool if alert is valid
func (a *Alert) isValid() bool {
	return a.isSimpleForm() && len(a.Body) == 0
}
<|endoftext|>


<commit_before>package endpoints

import (
	"fmt"
	"net"
	"net/http"
	"sync"

	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
	log "gopkg.in/inconshreveable/log15.v2"
	tomb "gopkg.in/tomb.v2"
)

// Config holds various configuration values that affect LXD endpoints
// initialization.
type Config struct {
	// The LXD var directory to create Unix sockets in.
	Dir string

	// HTTP server handling requests for the LXD RESTful API.
	RestServer *http.Server

	// HTTP server for the internal /dev/lxd API exposed to containers.
	DevLxdServer *http.Server

	// The TLS keypair and optional CA to use for the network endpoint. It
	// must be always provided, since the pubblic key will be included in
	// the response of the /1.0 REST API as part of the server info.
	//
	// It can be updated after the endpoints are up using NetworkUpdateCert().
	Cert *shared.CertInfo

	// System group name to which the unix socket for the local endpoint should be
	// chgrp'ed when starting. The default is to use the process group. An empty
	// string means "use the default".
	LocalUnixSocketGroup string

	// NetworkSetAddress sets the address for the network endpoint. If not
	// set, the network endpoint won't be started (unless it's passed via
	// socket-based activation).
	//
	// It can be updated after the endpoints are up using UpdateNetworkAddress().
	NetworkAddress string
}

// Up brings up all applicable LXD endpoints and starts accepting HTTP
// requests.
//
// The endpoints will be activated in the following order and according to the
// following rules:
//
// local endpoint (unix socket)
// ----------------------------
//
// If socket-based activation is detected, look for a unix socket among the
// inherited file descriptors and use it for the local endpoint (or if no such
// file descriptor exists, don't bring up the local endpoint at all).
//
// If no socket-based activation is detected, create a unix socket using the
// default <lxd-var-dir>/unix.socket path. The file mode of this socket will be set
// to 660, the file owner will be set to the process' UID, and the file group
// will be set to the process GID, or to the GID of the system group name
// specified via config.LocalUnixSocketGroup.
//
// devlxd endpoint (unix socket)
// ----------------------------
//
// Created using <lxd-var-dir>/devlxd/sock, with file mode set to 666 (actual
// authorization will be performed by the HTTP server using the socket ucred
// struct).
//
// remote endpoint (TCP socket with TLS)
// -------------------------------------
//
// If socket-based activation is detected, look for a network socket among the
// inherited file descriptors and use it for the network endpoint.
//
// If a network address was set via config.NetworkAddress, then close any listener
// that was detected via socket-based activation and create a new network
// socket bound to the given address.
//
// The network endpoint socket will use TLS encryption, using the certificate
// keypair and CA passed via config.Cert.
func Up(config *Config) (*Endpoints, error) {
	if config.Dir == "" {
		return nil, fmt.Errorf("no directory configured")
	}
	if config.RestServer == nil {
		return nil, fmt.Errorf("no REST server configured")
	}
	if config.DevLxdServer == nil {
		return nil, fmt.Errorf("no devlxd server configured")
	}
	if config.Cert == nil {
		return nil, fmt.Errorf("no TLS certificate configured")
	}

	endpoints := &Endpoints{
		systemdListenFDsStart: util.SystemdListenFDsStart,
	}

	err := endpoints.up(config)
	if err != nil {
		endpoints.Down()
		return nil, err
	}
	return endpoints, nil
}

// Endpoints are in charge of bringing up and down the HTTP endpoints for
// serving the LXD RESTful API.
//
// When LXD starts up, they start listen to the appropriate sockets and attach
// the relevant HTTP handlers to them. When LXD shuts down they close all
// sockets.
type Endpoints struct {
	tomb      *tomb.Tomb            // Controls the HTTP servers shutdown.
	mu        sync.RWMutex          // Serialize access to internal state.
	listeners map[kind]net.Listener // Activer listeners by endpoint type.
	servers   map[kind]*http.Server // HTTP servers by endpoint type.
	cert      *shared.CertInfo      // Keypair and CA to use for TLS.

	systemdListenFDsStart int // First socket activation FD, for tests.
}

// Up brings up all configured endpoints and starts accepting HTTP requests.
func (e *Endpoints) up(config *Config) error {
	e.mu.Lock()
	defer e.mu.Unlock()

	e.servers = map[kind]*http.Server{
		devlxd:  config.DevLxdServer,
		local:   config.RestServer,
		network: config.RestServer,
	}
	e.cert = config.Cert

	var err error

	// Check for socket activation.
	systemdListeners := util.GetListeners(e.systemdListenFDsStart)
	if len(systemdListeners) > 0 {
		logger.Infof("LXD is socket activated")
		e.listeners = activatedListeners(systemdListeners, e.cert)
	} else {
		logger.Infof("LXD isn't socket activated")
		e.listeners = map[kind]net.Listener{}

		e.listeners[local], err = localCreateListener(config.Dir, config.LocalUnixSocketGroup)
		if err != nil {
			return fmt.Errorf("local endpoint: %v", err)
		}
	}

	// Start the devlxd listener
	e.listeners[devlxd], err = createDevLxdlListener(config.Dir)
	if err != nil {
		return err
	}

	if config.NetworkAddress != "" {
		listener, ok := e.listeners[network]
		if ok {
			logger.Infof("Replacing inherited TCP socket with configured one")
			listener.Close()
		}
		// Errors here are not fatal and are just logged.
		e.listeners[network] = networkCreateListener(config.NetworkAddress, e.cert)
	}

	logger.Infof("Starting /dev/lxd handler:")
	e.serveHTTP(devlxd)

	logger.Infof("REST API daemon:")
	e.serveHTTP(local)
	e.serveHTTP(network)

	return nil
}

// Down brings down all endpoints and stops serving HTTP requests.
func (e *Endpoints) Down() error {
	e.mu.Lock()
	defer e.mu.Unlock()

	logger.Infof("Stopping REST API handler:")
	err := e.closeListener(network)
	if err != nil {
		return err
	}
	err = e.closeListener(local)
	if err != nil {
		return err
	}

	logger.Infof("Stopping /dev/lxd handler")
	err = e.closeListener(devlxd)
	if err != nil {
		return err
	}

	if e.tomb != nil {
		e.tomb.Kill(nil)
		e.tomb.Wait()
	}

	return nil
}

// Start an HTTP server for the endpoint associated with the given code.
func (e *Endpoints) serveHTTP(kind kind) {
	listener := e.listeners[kind]

	if listener == nil {
		return
	}

	message := fmt.Sprintf(" - binding %s", descriptions[kind])
	logger.Info(message, log.Ctx{"socket": listener.Addr()})

	server := e.servers[kind]

	// Defer the creation of the tomb, so Down() doesn't wait on it unless
	// we actually have spawned at least a server.
	if e.tomb == nil {
		e.tomb = &tomb.Tomb{}
	}

	e.tomb.Go(func() error {
		server.Serve(listener)
		return nil
	})
}

// Stop the HTTP server of the endpoint associated with the given code. The
// associated socket will be shutdown too.
func (e *Endpoints) closeListener(code kind) error {
	listener := e.listeners[code]
	if listener == nil {
		return nil
	}
	delete(e.listeners, code)

	logger.Info(" - closing socket", log.Ctx{"socket": listener.Addr()})

	return listener.Close()
}

// Use the listeners associated with the file descriptors passed via
// socket-based activation.
func activatedListeners(systemdListeners []net.Listener, cert *shared.CertInfo) map[kind]net.Listener {
	listeners := map[kind]net.Listener{}
	for _, listener := range systemdListeners {
		var code kind
		switch listener.(type) {
		case *net.UnixListener:
			code = local
		case *net.TCPListener:
			code = network
			listener = networkTLSListener(listener, cert)
		default:
			continue
		}
		listeners[code] = listener
	}
	return listeners
}

// Numeric code identifying a specific API endpoint type.
type kind int

// Numeric codes identifying the various endpoints.
const (
	local kind = iota
	devlxd
	network
)

// Human-readable descriptions of the various kinds of endpoints.
var descriptions = map[kind]string{
	local:   "Unix socket",
	devlxd:  "devlxd socket",
	network: "TCP socket",
}
<commit_msg>Rename variable "code" to "kind" for consistency<commit_after>package endpoints

import (
	"fmt"
	"net"
	"net/http"
	"sync"

	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
	log "gopkg.in/inconshreveable/log15.v2"
	tomb "gopkg.in/tomb.v2"
)

// Config holds various configuration values that affect LXD endpoints
// initialization.
type Config struct {
	// The LXD var directory to create Unix sockets in.
	Dir string

	// HTTP server handling requests for the LXD RESTful API.
	RestServer *http.Server

	// HTTP server for the internal /dev/lxd API exposed to containers.
	DevLxdServer *http.Server

	// The TLS keypair and optional CA to use for the network endpoint. It
	// must be always provided, since the pubblic key will be included in
	// the response of the /1.0 REST API as part of the server info.
	//
	// It can be updated after the endpoints are up using NetworkUpdateCert().
	Cert *shared.CertInfo

	// System group name to which the unix socket for the local endpoint should be
	// chgrp'ed when starting. The default is to use the process group. An empty
	// string means "use the default".
	LocalUnixSocketGroup string

	// NetworkSetAddress sets the address for the network endpoint. If not
	// set, the network endpoint won't be started (unless it's passed via
	// socket-based activation).
	//
	// It can be updated after the endpoints are up using UpdateNetworkAddress().
	NetworkAddress string
}

// Up brings up all applicable LXD endpoints and starts accepting HTTP
// requests.
//
// The endpoints will be activated in the following order and according to the
// following rules:
//
// local endpoint (unix socket)
// ----------------------------
//
// If socket-based activation is detected, look for a unix socket among the
// inherited file descriptors and use it for the local endpoint (or if no such
// file descriptor exists, don't bring up the local endpoint at all).
//
// If no socket-based activation is detected, create a unix socket using the
// default <lxd-var-dir>/unix.socket path. The file mode of this socket will be set
// to 660, the file owner will be set to the process' UID, and the file group
// will be set to the process GID, or to the GID of the system group name
// specified via config.LocalUnixSocketGroup.
//
// devlxd endpoint (unix socket)
// ----------------------------
//
// Created using <lxd-var-dir>/devlxd/sock, with file mode set to 666 (actual
// authorization will be performed by the HTTP server using the socket ucred
// struct).
//
// remote endpoint (TCP socket with TLS)
// -------------------------------------
//
// If socket-based activation is detected, look for a network socket among the
// inherited file descriptors and use it for the network endpoint.
//
// If a network address was set via config.NetworkAddress, then close any listener
// that was detected via socket-based activation and create a new network
// socket bound to the given address.
//
// The network endpoint socket will use TLS encryption, using the certificate
// keypair and CA passed via config.Cert.
func Up(config *Config) (*Endpoints, error) {
	if config.Dir == "" {
		return nil, fmt.Errorf("no directory configured")
	}
	if config.RestServer == nil {
		return nil, fmt.Errorf("no REST server configured")
	}
	if config.DevLxdServer == nil {
		return nil, fmt.Errorf("no devlxd server configured")
	}
	if config.Cert == nil {
		return nil, fmt.Errorf("no TLS certificate configured")
	}

	endpoints := &Endpoints{
		systemdListenFDsStart: util.SystemdListenFDsStart,
	}

	err := endpoints.up(config)
	if err != nil {
		endpoints.Down()
		return nil, err
	}
	return endpoints, nil
}

// Endpoints are in charge of bringing up and down the HTTP endpoints for
// serving the LXD RESTful API.
//
// When LXD starts up, they start listen to the appropriate sockets and attach
// the relevant HTTP handlers to them. When LXD shuts down they close all
// sockets.
type Endpoints struct {
	tomb      *tomb.Tomb            // Controls the HTTP servers shutdown.
	mu        sync.RWMutex          // Serialize access to internal state.
	listeners map[kind]net.Listener // Activer listeners by endpoint type.
	servers   map[kind]*http.Server // HTTP servers by endpoint type.
	cert      *shared.CertInfo      // Keypair and CA to use for TLS.

	systemdListenFDsStart int // First socket activation FD, for tests.
}

// Up brings up all configured endpoints and starts accepting HTTP requests.
func (e *Endpoints) up(config *Config) error {
	e.mu.Lock()
	defer e.mu.Unlock()

	e.servers = map[kind]*http.Server{
		devlxd:  config.DevLxdServer,
		local:   config.RestServer,
		network: config.RestServer,
	}
	e.cert = config.Cert

	var err error

	// Check for socket activation.
	systemdListeners := util.GetListeners(e.systemdListenFDsStart)
	if len(systemdListeners) > 0 {
		logger.Infof("LXD is socket activated")
		e.listeners = activatedListeners(systemdListeners, e.cert)
	} else {
		logger.Infof("LXD isn't socket activated")
		e.listeners = map[kind]net.Listener{}

		e.listeners[local], err = localCreateListener(config.Dir, config.LocalUnixSocketGroup)
		if err != nil {
			return fmt.Errorf("local endpoint: %v", err)
		}
	}

	// Start the devlxd listener
	e.listeners[devlxd], err = createDevLxdlListener(config.Dir)
	if err != nil {
		return err
	}

	if config.NetworkAddress != "" {
		listener, ok := e.listeners[network]
		if ok {
			logger.Infof("Replacing inherited TCP socket with configured one")
			listener.Close()
		}
		// Errors here are not fatal and are just logged.
		e.listeners[network] = networkCreateListener(config.NetworkAddress, e.cert)
	}

	logger.Infof("Starting /dev/lxd handler:")
	e.serveHTTP(devlxd)

	logger.Infof("REST API daemon:")
	e.serveHTTP(local)
	e.serveHTTP(network)

	return nil
}

// Down brings down all endpoints and stops serving HTTP requests.
func (e *Endpoints) Down() error {
	e.mu.Lock()
	defer e.mu.Unlock()

	logger.Infof("Stopping REST API handler:")
	err := e.closeListener(network)
	if err != nil {
		return err
	}
	err = e.closeListener(local)
	if err != nil {
		return err
	}

	logger.Infof("Stopping /dev/lxd handler")
	err = e.closeListener(devlxd)
	if err != nil {
		return err
	}

	if e.tomb != nil {
		e.tomb.Kill(nil)
		e.tomb.Wait()
	}

	return nil
}

// Start an HTTP server for the endpoint associated with the given code.
func (e *Endpoints) serveHTTP(kind kind) {
	listener := e.listeners[kind]

	if listener == nil {
		return
	}

	message := fmt.Sprintf(" - binding %s", descriptions[kind])
	logger.Info(message, log.Ctx{"socket": listener.Addr()})

	server := e.servers[kind]

	// Defer the creation of the tomb, so Down() doesn't wait on it unless
	// we actually have spawned at least a server.
	if e.tomb == nil {
		e.tomb = &tomb.Tomb{}
	}

	e.tomb.Go(func() error {
		server.Serve(listener)
		return nil
	})
}

// Stop the HTTP server of the endpoint associated with the given code. The
// associated socket will be shutdown too.
func (e *Endpoints) closeListener(kind kind) error {
	listener := e.listeners[kind]
	if listener == nil {
		return nil
	}
	delete(e.listeners, kind)

	logger.Info(" - closing socket", log.Ctx{"socket": listener.Addr()})

	return listener.Close()
}

// Use the listeners associated with the file descriptors passed via
// socket-based activation.
func activatedListeners(systemdListeners []net.Listener, cert *shared.CertInfo) map[kind]net.Listener {
	listeners := map[kind]net.Listener{}
	for _, listener := range systemdListeners {
		var kind kind
		switch listener.(type) {
		case *net.UnixListener:
			kind = local
		case *net.TCPListener:
			kind = network
			listener = networkTLSListener(listener, cert)
		default:
			continue
		}
		listeners[kind] = listener
	}
	return listeners
}

// Numeric code identifying a specific API endpoint type.
type kind int

// Numeric codes identifying the various endpoints.
const (
	local kind = iota
	devlxd
	network
)

// Human-readable descriptions of the various kinds of endpoints.
var descriptions = map[kind]string{
	local:   "Unix socket",
	devlxd:  "devlxd socket",
	network: "TCP socket",
}
<|endoftext|>


<commit_before>package router

import (
	"net/http"
	"strings"

	"github.com/agupta666/elf/actions"
)

// Router is a basic http router for routes added from console
type Router struct {
}

func (rt *Router) match(path string) (actions.Action, string, bool) {
	// ac, ok := routes[path]

	for k, v := range routes {
		if strings.HasPrefix(path, k) {
			return v, k, true
		}
	}
	return nil, "", false
}

func (rt *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	action, pattern, ok := rt.match(r.URL.Path)

	if !ok {
		http.Error(w, "No action defined for this route", http.StatusInternalServerError)
		return
	}

	action.SetPattern(pattern)
	err := action.Exec(w, r)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}
<commit_msg>fix status code for route not found<commit_after>package router

import (
	"net/http"
	"strings"

	"github.com/agupta666/elf/actions"
)

// Router is a basic http router for routes added from console
type Router struct {
}

func (rt *Router) match(path string) (actions.Action, string, bool) {
	// ac, ok := routes[path]

	for k, v := range routes {
		if strings.HasPrefix(path, k) {
			return v, k, true
		}
	}
	return nil, "", false
}

func (rt *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	action, pattern, ok := rt.match(r.URL.Path)

	if !ok {
		http.Error(w, "No action defined for this route", http.StatusNotFound)
		return
	}

	action.SetPattern(pattern)
	err := action.Exec(w, r)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}
<|endoftext|>