<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 main

import (
	"bytes"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"

	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
)

var (
	depl1Replicas int32 = 200
	depl2Replicas int32 = 5
)

type mockDeploymentStore struct {
	f func() ([]v1beta1.Deployment, error)
}

func (ds mockDeploymentStore) List() (deployments []v1beta1.Deployment, err error) {
	return ds.f()
}

func TestDeploymentCollector(t *testing.T) {
	// Fixed metadata on type and help text. We prepend this to every expected
	// output so we only have to modify a single place when doing adjustments.
	const metadata = `
		# HELP kube_deployment_metadata_generation Sequence number representing a specific generation of the desired state.
		# TYPE kube_deployment_metadata_generation gauge
		# HELP kube_deployment_spec_paused Whether the deployment is paused and will not be processed by the deployment controller.
		# TYPE kube_deployment_spec_paused gauge
		# HELP kube_deployment_spec_replicas Number of desired pods for a deployment.
		# TYPE kube_deployment_spec_replicas gauge
		# HELP kube_deployment_status_replicas The number of replicas per deployment.
		# TYPE kube_deployment_status_replicas gauge
		# HELP kube_deployment_status_replicas_available The number of available replicas per deployment.
		# TYPE kube_deployment_status_replicas_available gauge
		# HELP kube_deployment_status_replicas_unavailable The number of unavailable replicas per deployment.
		# TYPE kube_deployment_status_replicas_unavailable gauge
		# HELP kube_deployment_status_replicas_updated The number of updated replicas per deployment.
		# TYPE kube_deployment_status_replicas_updated gauge
		# HELP kube_deployment_status_observed_generation The generation observed by the deployment controller.
		# TYPE kube_deployment_status_observed_generation gauge
	`
	cases := []struct {
		depls []v1beta1.Deployment
		want  string
	}{
		{
			depls: []v1beta1.Deployment{
				{
					ObjectMeta: v1.ObjectMeta{
						Name:       "depl1",
						Namespace:  "ns1",
						Generation: 21,
					},
					Status: v1beta1.DeploymentStatus{
						Replicas:            15,
						AvailableReplicas:   10,
						UnavailableReplicas: 5,
						UpdatedReplicas:     2,
						ObservedGeneration:  111,
					},
					Spec: v1beta1.DeploymentSpec{
						Replicas: &depl1Replicas,
					},
				}, {
					ObjectMeta: v1.ObjectMeta{
						Name:       "depl2",
						Namespace:  "ns2",
						Generation: 14,
					},
					Status: v1beta1.DeploymentStatus{
						Replicas:            10,
						AvailableReplicas:   5,
						UnavailableReplicas: 0,
						UpdatedReplicas:     1,
						ObservedGeneration:  1111,
					},
					Spec: v1beta1.DeploymentSpec{
						Paused:   true,
						Replicas: &depl2Replicas,
					},
				},
			},
			want: metadata + `
				kube_deployment_metadata_generation{namespace="ns1",deployment="depl1"} 21
				kube_deployment_metadata_generation{namespace="ns2",deployment="depl2"} 14
				kube_deployment_spec_paused{namespace="ns1",deployment="depl1"} 0
				kube_deployment_spec_paused{namespace="ns2",deployment="depl2"} 1
				kube_deployment_spec_replicas{namespace="ns1",deployment="depl1"} 200
				kube_deployment_spec_replicas{namespace="ns2",deployment="depl2"} 5
				kube_deployment_status_replicas{namespace="ns1",deployment="depl1"} 15
				kube_deployment_status_replicas{namespace="ns2",deployment="depl2"} 10
				kube_deployment_status_replicas_available{namespace="ns1",deployment="depl1"} 10
				kube_deployment_status_replicas_available{namespace="ns2",deployment="depl2"} 5
				kube_deployment_status_replicas_unavailable{namespace="ns1",deployment="depl1"} 5
				kube_deployment_status_replicas_unavailable{namespace="ns2",deployment="depl2"} 0
				kube_deployment_status_replicas_updated{namespace="ns1",deployment="depl1"} 2
				kube_deployment_status_replicas_updated{namespace="ns2",deployment="depl2"} 1
				kube_deployment_status_observed_generation{namespace="ns1",deployment="depl1"} 111
				kube_deployment_status_observed_generation{namespace="ns2",deployment="depl2"} 1111
			`,
		},
	}
	for _, c := range cases {
		dc := &deploymentCollector{
			store: mockDeploymentStore{
				f: func() ([]v1beta1.Deployment, error) { return c.depls, nil },
			},
		}
		if err := gatherAndCompare(dc, c.want, nil); err != nil {
			t.Errorf("unexpected collecting result:\n%s", err)
		}
	}
}

// gatherAndCompare retrieves all metrics exposed by a collector and compares it
// to an expected output in the Prometheus text exposition format.
// metricNames allows only comparing the given metrics. All are compared if it's nil.
func gatherAndCompare(c prometheus.Collector, expected string, metricNames []string) error {
	expected = removeUnusedWhitespace(expected)

	reg := prometheus.NewPedanticRegistry()
	if err := reg.Register(c); err != nil {
		return fmt.Errorf("registering collector failed: %s", err)
	}
	metrics, err := reg.Gather()
	if err != nil {
		return fmt.Errorf("gathering metrics failed: %s", err)
	}
	if metricNames != nil {
		metrics = filterMetrics(metrics, metricNames)
	}
	var tp expfmt.TextParser
	expectedMetrics, err := tp.TextToMetricFamilies(bytes.NewReader([]byte(expected)))
	if err != nil {
		return fmt.Errorf("parsing expected metrics failed: %s", err)
	}

	if !reflect.DeepEqual(metrics, normalizeMetricFamilies(expectedMetrics)) {
		// Encode the gathered output to the readbale text format for comparison.
		var buf1 bytes.Buffer
		enc := expfmt.NewEncoder(&buf1, expfmt.FmtText)
		for _, mf := range metrics {
			if err := enc.Encode(mf); err != nil {
				return fmt.Errorf("encoding result failed: %s", err)
			}
		}
		// Encode normalized expected metrics again to generate them in the same ordering
		// the registry does to spot differences more easily.
		var buf2 bytes.Buffer
		enc = expfmt.NewEncoder(&buf2, expfmt.FmtText)
		for _, mf := range normalizeMetricFamilies(expectedMetrics) {
			if err := enc.Encode(mf); err != nil {
				return fmt.Errorf("encoding result failed: %s", err)
			}
		}

		return fmt.Errorf(`
metric output does not match expectation; want:

%s

got:

%s
`, buf2.String(), buf1.String())
	}
	return nil
}

func filterMetrics(metrics []*dto.MetricFamily, names []string) []*dto.MetricFamily {
	var filtered []*dto.MetricFamily
	for _, m := range metrics {
		drop := true
		for _, name := range names {
			if m.GetName() == name {
				drop = false
				break
			}
		}
		if !drop {
			filtered = append(filtered, m)
		}
	}
	return filtered
}

func removeUnusedWhitespace(s string) string {
	var (
		trimmedLine  string
		trimmedLines []string
		lines        = strings.Split(s, "\n")
	)

	for _, l := range lines {
		trimmedLine = strings.TrimSpace(l)

		if len(trimmedLine) > 0 {
			trimmedLines = append(trimmedLines, trimmedLine)
		}
	}

	// The Prometheus metrics representation parser expects an empty line at the
	// end otherwise fails with an unexpected EOF error.
	return strings.Join(trimmedLines, "\n") + "\n"
}

// The below sorting code is copied form the Prometheus client library modulo the added
// label pair sorting.
// https://github.com/prometheus/client_golang/blob/ea6e1db4cb8127eeb0b6954f7320363e5451820f/prometheus/registry.go#L642-L684

// metricSorter is a sortable slice of *dto.Metric.
type metricSorter []*dto.Metric

func (s metricSorter) Len() int {
	return len(s)
}

func (s metricSorter) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s metricSorter) Less(i, j int) bool {
	sort.Sort(prometheus.LabelPairSorter(s[i].Label))
	sort.Sort(prometheus.LabelPairSorter(s[j].Label))

	if len(s[i].Label) != len(s[j].Label) {
		return len(s[i].Label) < len(s[j].Label)
	}

	for n, lp := range s[i].Label {
		vi := lp.GetValue()
		vj := s[j].Label[n].GetValue()
		if vi != vj {
			return vi < vj
		}
	}

	if s[i].TimestampMs == nil {
		return false
	}
	if s[j].TimestampMs == nil {
		return true
	}
	return s[i].GetTimestampMs() < s[j].GetTimestampMs()
}

// normalizeMetricFamilies returns a MetricFamily slice with empty
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
// the slice, with the contained Metrics sorted within each MetricFamily.
func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
	for _, mf := range metricFamiliesByName {
		sort.Sort(metricSorter(mf.Metric))
	}
	names := make([]string, 0, len(metricFamiliesByName))
	for name, mf := range metricFamiliesByName {
		if len(mf.Metric) > 0 {
			names = append(names, name)
		}
	}
	sort.Strings(names)
	result := make([]*dto.MetricFamily, 0, len(names))
	for _, name := range names {
		result = append(result, metricFamiliesByName[name])
	}
	return result
}
<commit_msg>Update Deployment test for RollingUpdate MaxUnavailable metric<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 main

import (
	"bytes"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"

	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/pkg/util/intstr"
)

var (
	depl1Replicas int32 = 200
	depl2Replicas int32 = 5

	depl1MaxUnavailable = intstr.FromInt(10)
	depl2MaxUnavailable = intstr.FromString("20%")
)

type mockDeploymentStore struct {
	f func() ([]v1beta1.Deployment, error)
}

func (ds mockDeploymentStore) List() (deployments []v1beta1.Deployment, err error) {
	return ds.f()
}

func TestDeploymentCollector(t *testing.T) {
	// Fixed metadata on type and help text. We prepend this to every expected
	// output so we only have to modify a single place when doing adjustments.
	const metadata = `
		# HELP kube_deployment_metadata_generation Sequence number representing a specific generation of the desired state.
		# TYPE kube_deployment_metadata_generation gauge
		# HELP kube_deployment_spec_paused Whether the deployment is paused and will not be processed by the deployment controller.
		# TYPE kube_deployment_spec_paused gauge
		# HELP kube_deployment_spec_replicas Number of desired pods for a deployment.
		# TYPE kube_deployment_spec_replicas gauge
		# HELP kube_deployment_status_replicas The number of replicas per deployment.
		# TYPE kube_deployment_status_replicas gauge
		# HELP kube_deployment_status_replicas_available The number of available replicas per deployment.
		# TYPE kube_deployment_status_replicas_available gauge
		# HELP kube_deployment_status_replicas_unavailable The number of unavailable replicas per deployment.
		# TYPE kube_deployment_status_replicas_unavailable gauge
		# HELP kube_deployment_status_replicas_updated The number of updated replicas per deployment.
		# TYPE kube_deployment_status_replicas_updated gauge
		# HELP kube_deployment_status_observed_generation The generation observed by the deployment controller.
		# TYPE kube_deployment_status_observed_generation gauge
                # HELP kube_deployment_spec_strategy_rollingupdate_max_unavailable Maximum number of unavailable replicas during a rolling update of a deployment.
		# TYPE kube_deployment_spec_strategy_rollingupdate_max_unavailable gauge
	`
	cases := []struct {
		depls []v1beta1.Deployment
		want  string
	}{
		{
			depls: []v1beta1.Deployment{
				{
					ObjectMeta: v1.ObjectMeta{
						Name:       "depl1",
						Namespace:  "ns1",
						Generation: 21,
					},
					Status: v1beta1.DeploymentStatus{
						Replicas:            15,
						AvailableReplicas:   10,
						UnavailableReplicas: 5,
						UpdatedReplicas:     2,
						ObservedGeneration:  111,
					},
					Spec: v1beta1.DeploymentSpec{
						Replicas: &depl1Replicas,
						Strategy: v1beta1.DeploymentStrategy{
							RollingUpdate: &v1beta1.RollingUpdateDeployment{
								MaxUnavailable: &depl1MaxUnavailable,
							},
						},
					},
				}, {
					ObjectMeta: v1.ObjectMeta{
						Name:       "depl2",
						Namespace:  "ns2",
						Generation: 14,
					},
					Status: v1beta1.DeploymentStatus{
						Replicas:            10,
						AvailableReplicas:   5,
						UnavailableReplicas: 0,
						UpdatedReplicas:     1,
						ObservedGeneration:  1111,
					},
					Spec: v1beta1.DeploymentSpec{
						Paused:   true,
						Replicas: &depl2Replicas,
						Strategy: v1beta1.DeploymentStrategy{
							RollingUpdate: &v1beta1.RollingUpdateDeployment{
								MaxUnavailable: &depl2MaxUnavailable,
							},
						},
					},
				},
			},
			want: metadata + `
				kube_deployment_metadata_generation{namespace="ns1",deployment="depl1"} 21
				kube_deployment_metadata_generation{namespace="ns2",deployment="depl2"} 14
				kube_deployment_spec_paused{namespace="ns1",deployment="depl1"} 0
				kube_deployment_spec_paused{namespace="ns2",deployment="depl2"} 1
				kube_deployment_spec_replicas{namespace="ns1",deployment="depl1"} 200
				kube_deployment_spec_replicas{namespace="ns2",deployment="depl2"} 5
				kube_deployment_status_replicas{namespace="ns1",deployment="depl1"} 15
				kube_deployment_status_replicas{namespace="ns2",deployment="depl2"} 10
				kube_deployment_status_replicas_available{namespace="ns1",deployment="depl1"} 10
				kube_deployment_status_replicas_available{namespace="ns2",deployment="depl2"} 5
				kube_deployment_status_replicas_unavailable{namespace="ns1",deployment="depl1"} 5
				kube_deployment_status_replicas_unavailable{namespace="ns2",deployment="depl2"} 0
				kube_deployment_status_replicas_updated{namespace="ns1",deployment="depl1"} 2
				kube_deployment_status_replicas_updated{namespace="ns2",deployment="depl2"} 1
				kube_deployment_status_observed_generation{namespace="ns1",deployment="depl1"} 111
				kube_deployment_status_observed_generation{namespace="ns2",deployment="depl2"} 1111
				kube_deployment_spec_strategy_rollingupdate_max_unavailable{namespace="ns1",deployment="depl1"} 10
				kube_deployment_spec_strategy_rollingupdate_max_unavailable{namespace="ns2",deployment="depl2"} 1
			`,
		},
	}
	for _, c := range cases {
		dc := &deploymentCollector{
			store: mockDeploymentStore{
				f: func() ([]v1beta1.Deployment, error) { return c.depls, nil },
			},
		}
		if err := gatherAndCompare(dc, c.want, nil); err != nil {
			t.Errorf("unexpected collecting result:\n%s", err)
		}
	}
}

// gatherAndCompare retrieves all metrics exposed by a collector and compares it
// to an expected output in the Prometheus text exposition format.
// metricNames allows only comparing the given metrics. All are compared if it's nil.
func gatherAndCompare(c prometheus.Collector, expected string, metricNames []string) error {
	expected = removeUnusedWhitespace(expected)

	reg := prometheus.NewPedanticRegistry()
	if err := reg.Register(c); err != nil {
		return fmt.Errorf("registering collector failed: %s", err)
	}
	metrics, err := reg.Gather()
	if err != nil {
		return fmt.Errorf("gathering metrics failed: %s", err)
	}
	if metricNames != nil {
		metrics = filterMetrics(metrics, metricNames)
	}
	var tp expfmt.TextParser
	expectedMetrics, err := tp.TextToMetricFamilies(bytes.NewReader([]byte(expected)))
	if err != nil {
		return fmt.Errorf("parsing expected metrics failed: %s", err)
	}

	if !reflect.DeepEqual(metrics, normalizeMetricFamilies(expectedMetrics)) {
		// Encode the gathered output to the readbale text format for comparison.
		var buf1 bytes.Buffer
		enc := expfmt.NewEncoder(&buf1, expfmt.FmtText)
		for _, mf := range metrics {
			if err := enc.Encode(mf); err != nil {
				return fmt.Errorf("encoding result failed: %s", err)
			}
		}
		// Encode normalized expected metrics again to generate them in the same ordering
		// the registry does to spot differences more easily.
		var buf2 bytes.Buffer
		enc = expfmt.NewEncoder(&buf2, expfmt.FmtText)
		for _, mf := range normalizeMetricFamilies(expectedMetrics) {
			if err := enc.Encode(mf); err != nil {
				return fmt.Errorf("encoding result failed: %s", err)
			}
		}

		return fmt.Errorf(`
metric output does not match expectation; want:

%s

got:

%s
`, buf2.String(), buf1.String())
	}
	return nil
}

func filterMetrics(metrics []*dto.MetricFamily, names []string) []*dto.MetricFamily {
	var filtered []*dto.MetricFamily
	for _, m := range metrics {
		drop := true
		for _, name := range names {
			if m.GetName() == name {
				drop = false
				break
			}
		}
		if !drop {
			filtered = append(filtered, m)
		}
	}
	return filtered
}

func removeUnusedWhitespace(s string) string {
	var (
		trimmedLine  string
		trimmedLines []string
		lines        = strings.Split(s, "\n")
	)

	for _, l := range lines {
		trimmedLine = strings.TrimSpace(l)

		if len(trimmedLine) > 0 {
			trimmedLines = append(trimmedLines, trimmedLine)
		}
	}

	// The Prometheus metrics representation parser expects an empty line at the
	// end otherwise fails with an unexpected EOF error.
	return strings.Join(trimmedLines, "\n") + "\n"
}

// The below sorting code is copied form the Prometheus client library modulo the added
// label pair sorting.
// https://github.com/prometheus/client_golang/blob/ea6e1db4cb8127eeb0b6954f7320363e5451820f/prometheus/registry.go#L642-L684

// metricSorter is a sortable slice of *dto.Metric.
type metricSorter []*dto.Metric

func (s metricSorter) Len() int {
	return len(s)
}

func (s metricSorter) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s metricSorter) Less(i, j int) bool {
	sort.Sort(prometheus.LabelPairSorter(s[i].Label))
	sort.Sort(prometheus.LabelPairSorter(s[j].Label))

	if len(s[i].Label) != len(s[j].Label) {
		return len(s[i].Label) < len(s[j].Label)
	}

	for n, lp := range s[i].Label {
		vi := lp.GetValue()
		vj := s[j].Label[n].GetValue()
		if vi != vj {
			return vi < vj
		}
	}

	if s[i].TimestampMs == nil {
		return false
	}
	if s[j].TimestampMs == nil {
		return true
	}
	return s[i].GetTimestampMs() < s[j].GetTimestampMs()
}

// normalizeMetricFamilies returns a MetricFamily slice with empty
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
// the slice, with the contained Metrics sorted within each MetricFamily.
func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
	for _, mf := range metricFamiliesByName {
		sort.Sort(metricSorter(mf.Metric))
	}
	names := make([]string, 0, len(metricFamiliesByName))
	for name, mf := range metricFamiliesByName {
		if len(mf.Metric) > 0 {
			names = append(names, name)
		}
	}
	sort.Strings(names)
	result := make([]*dto.MetricFamily, 0, len(names))
	for _, name := range names {
		result = append(result, metricFamiliesByName[name])
	}
	return result
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/xeipuuv/gojsonschema"
)

// SuiteFile describes location of the suite file.
type SuiteFile struct {

	// Path to the file.
	Path string

	// Base directory from file is loaded.
	BaseDir string

	// Ext is preserved to create 'clean' suite name
	Ext string

	// If true then skip all test cases in this suite
	Ignored bool
}

// RelDir returns difference between Path and BaseDir.
func (sf SuiteFile) RelDir() string {
	dir, _ := filepath.Rel(sf.BaseDir, filepath.Dir(sf.Path))
	return dir
}

// ToSuite method deserializes suite representation to the object model.
func (sf SuiteFile) ToSuite() *TestSuite {
	if sf.Path == "" {
		return nil
	}

	path := sf.Path
	info, err := os.Lstat(path)

	if err != nil {
		return nil
	}

	if info.IsDir() {
		debug.Print("Ignore dir: " + sf.Path)
		return nil
	}

	content, e := ioutil.ReadFile(path)

	if e != nil {
		fmt.Println("Cannot read file:", path, "Error: ", e.Error())
		return nil
	}

	var rawCases []*TestCase
	err = json.Unmarshal(content, &rawCases)
	if err != nil {
		fmt.Println("Cannot parse file:", path, "Error: ", err.Error())
		return nil
	}

	var cases []TestCase
	for _, tc := range rawCases {
		if sf.Ignored {
			msg := "Ignored suite"
			tc.Ignore = &msg
		}
		cases = append(cases, *tc)
	}

	su := TestSuite{
		Name:  strings.TrimSuffix(info.Name(), sf.Ext),
		Dir:   sf.RelDir(),
		Cases: cases,
	}

	return &su
}

// SuiteFileIterator is an interface to iterate over a set of suite files
// in a given directory
type SuiteFileIterator interface {
	HasNext() bool
	Next() *SuiteFile
}

// DirSuiteFileIterator iterates over all suite files inside of specified root folder.
type DirSuiteFileIterator struct {
	RootDir   string
	SuiteExt  string
	XSuiteExt string

	files []SuiteFile
	pos   int
}

func (ds *DirSuiteFileIterator) init() {
	filepath.Walk(ds.RootDir, ds.addSuiteFile)
	debug.Print("Collected: ", len(ds.files))
}

func (ds *DirSuiteFileIterator) addSuiteFile(path string, info os.FileInfo, err error) error {
	if err != nil {
		return nil
	}

	if info.IsDir() {
		return nil
	}

	fileName := info.Name()

	isSuite := strings.HasSuffix(fileName, ds.SuiteExt)
	isXSuite := strings.HasSuffix(fileName, ds.XSuiteExt)

	if !(isSuite || isXSuite) {
		debug.Printf("Skipping non-suite file: %s\n", fileName)
		return nil
	}

	ext := ds.SuiteExt
	if isXSuite {
		ext = ds.XSuiteExt
	}

	ds.files = append(ds.files, SuiteFile{
		Path:    path,
		BaseDir: ds.RootDir,
		Ext:     ext,
		Ignored: isXSuite,
	})

	return nil
}

// HasNext returns true in case there is at least one more suite.
func (ds *DirSuiteFileIterator) HasNext() bool {
	return len(ds.files) > ds.pos
}

// Next returns next deserialized suite.
func (ds *DirSuiteFileIterator) Next() *SuiteFile {
	if len(ds.files) <= ds.pos {
		return nil
	}

	file := ds.files[ds.pos]
	ds.pos = ds.pos + 1
	return &file
}

// NewSuiteLoader returns channel of suites that are read from specified folder.
func NewSuiteLoader(rootDir, suiteExt, xsuiteExt string) <-chan TestSuite {
	channel := make(chan TestSuite)

	source := &DirSuiteFileIterator{RootDir: rootDir, SuiteExt: suiteExt, XSuiteExt: xsuiteExt}
	source.init()

	go func() {
		for source.HasNext() {
			sf := source.Next()
			if sf == nil {
				continue
			}
			suite := sf.ToSuite()
			if suite == nil {
				continue
			}

			channel <- *suite
		}

		close(channel)
	}()

	return channel
}

// ValidateSuites detects syntax errors in all test suites in the root directory.
func ValidateSuites(rootDir, suiteExt, xsuiteExt string) error {
	source := &DirSuiteFileIterator{RootDir: rootDir, SuiteExt: suiteExt, XSuiteExt: xsuiteExt}
	source.init()

	errs := make([]*SuiteFileError, 0)

	for source.HasNext() {
		sf := source.Next()

		if sf == nil {
			continue
		}

		err := validateSuite(sf.Path)
		if err != nil {
			errs = append(errs, &SuiteFileError{SuiteFile: sf, err: err})
		}
	}

	if len(errs) == 0 {
		return nil
	}

	return SuitesValidationError{errors: errs}
}

// SuiteFileError desctibes issue during loading of single test suite file
type SuiteFileError struct {
	SuiteFile *SuiteFile
	err       error
}

func (e SuiteFileError) Error() string {
	if e.err == nil {
		return ""
	}

	return fmt.Sprintf("%s: %s", e.SuiteFile.Path, e.err.Error())
}

// SuitesValidationError groups all errors for individual test suites
type SuitesValidationError struct {
	errors []*SuiteFileError
}

func (e SuitesValidationError) Error() string {
	msg := make([]string, 0)
	for _, err := range e.errors {
		msg = append(msg, err.Error())
	}

	return strings.Join(msg, "\n")
}

func isSuite(path string) bool {
	schemaLoader := gojsonschema.NewStringLoader(suiteShapeSchema)

	path, _ = filepath.Abs(path)
	documentLoader := gojsonschema.NewReferenceLoader("file:///" + path)

	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
	if err != nil {
		return false
	}

	return result.Valid()
}

func validateSuite(path string) error {
	schemaLoader := gojsonschema.NewStringLoader(suiteDetailedSchema)

	path, _ = filepath.Abs(path)
	documentLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(path))

	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
	if err != nil {
		return err
	}

	if !result.Valid() {
		msg := make([]string, 0)
		for _, desc := range result.Errors() {
			msg = append(msg, fmt.Sprintf("Field: %s, Error: %s", desc.Field(), desc.Description()))
		}

		return errors.New(strings.Join(msg, "\n"))
	}

	return nil
}

// used to detect suite
const suiteShapeSchema = `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string"
      },
      "calls": {
        "type": "array"
      }
    },
    "required": [
      "name",
      "calls"
    ]
  }
}
`

// used to validate suite
const suiteDetailedSchema = `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string"
      },
      "description": {
        "type": "string"
      },
      "ignore": {
        "type": "string",
        "minLength": 10
      },
      "calls": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "args": {
              "type": "object",
              "minProperties": 1
            },
            "on": {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "method": {
                  "type": "string",
                  "enum": [
                    "GET",
                    "POST",
                    "PUT",
                    "DELETE",
                    "HEAD",
                    "OPTIONS",
                    "PATCH",
                    "CONNECT",
                    "TRACE"
                  ]
                },
                "url": {
                  "type": "string"
                },
                "headers": {
                  "type": "object",
                  "minProperties": 1
                },
                "params": {
                  "type": "object",
                  "minProperties": 1
                },
                "body": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "object"
                    }
                  ]
                },
                "bodyFile": {
                  "type": "string"
                }
              },
              "required": [
                "method",
                "url"
              ]
            },
            "expect": {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "statusCode": {
                  "type": "integer"
                },
                "contentType": {
                  "type": "string"
                },
                "headers": {
                  "type": "object",
                  "minProperties": 1
                },
				"body": {
				  "type": "object",
                  "minProperties": 1
				},
				"exactBody": {
				  "type": "object",
                  "minProperties": 1
				},
				"bodyPath": {
				  "type": "object",
                  "minProperties": 1
                },
				"bodySchema": {
				  "type": "object"
				},
                "bodySchemaFile": {
                  "type": "string"
                },
                "bodySchemaURI": {
                  "type": "string"
                },
                "absent": {
                  "type": "array",
                  "minItems": 1
                }
              },
              "additionalProperties": false
            },
            "remember": {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "body": {
                  "type": "object",
                  "minProperties": 1
                },
                "headers": {
                  "type": "object",
                  "minProperties": 1
                }
              },
              "additionalProperties": false
            }
          },
          "required": [
            "on",
            "expect"
          ]
        }
      }
    },
    "required": [
      "calls"
    ]
  }
}
`
<commit_msg>close #84 stricter suite json scheme<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/xeipuuv/gojsonschema"
)

// SuiteFile describes location of the suite file.
type SuiteFile struct {

	// Path to the file.
	Path string

	// Base directory from file is loaded.
	BaseDir string

	// Ext is preserved to create 'clean' suite name
	Ext string

	// If true then skip all test cases in this suite
	Ignored bool
}

// RelDir returns difference between Path and BaseDir.
func (sf SuiteFile) RelDir() string {
	dir, _ := filepath.Rel(sf.BaseDir, filepath.Dir(sf.Path))
	return dir
}

// ToSuite method deserializes suite representation to the object model.
func (sf SuiteFile) ToSuite() *TestSuite {
	if sf.Path == "" {
		return nil
	}

	path := sf.Path
	info, err := os.Lstat(path)

	if err != nil {
		return nil
	}

	if info.IsDir() {
		debug.Print("Ignore dir: " + sf.Path)
		return nil
	}

	content, e := ioutil.ReadFile(path)

	if e != nil {
		fmt.Println("Cannot read file:", path, "Error: ", e.Error())
		return nil
	}

	var rawCases []*TestCase
	err = json.Unmarshal(content, &rawCases)
	if err != nil {
		fmt.Println("Cannot parse file:", path, "Error: ", err.Error())
		return nil
	}

	var cases []TestCase
	for _, tc := range rawCases {
		if sf.Ignored {
			msg := "Ignored suite"
			tc.Ignore = &msg
		}
		cases = append(cases, *tc)
	}

	su := TestSuite{
		Name:  strings.TrimSuffix(info.Name(), sf.Ext),
		Dir:   sf.RelDir(),
		Cases: cases,
	}

	return &su
}

// SuiteFileIterator is an interface to iterate over a set of suite files
// in a given directory
type SuiteFileIterator interface {
	HasNext() bool
	Next() *SuiteFile
}

// DirSuiteFileIterator iterates over all suite files inside of specified root folder.
type DirSuiteFileIterator struct {
	RootDir   string
	SuiteExt  string
	XSuiteExt string

	files []SuiteFile
	pos   int
}

func (ds *DirSuiteFileIterator) init() {
	filepath.Walk(ds.RootDir, ds.addSuiteFile)
	debug.Print("Collected: ", len(ds.files))
}

func (ds *DirSuiteFileIterator) addSuiteFile(path string, info os.FileInfo, err error) error {
	if err != nil {
		return nil
	}

	if info.IsDir() {
		return nil
	}

	fileName := info.Name()

	isSuite := strings.HasSuffix(fileName, ds.SuiteExt)
	isXSuite := strings.HasSuffix(fileName, ds.XSuiteExt)

	if !(isSuite || isXSuite) {
		debug.Printf("Skipping non-suite file: %s\n", fileName)
		return nil
	}

	ext := ds.SuiteExt
	if isXSuite {
		ext = ds.XSuiteExt
	}

	ds.files = append(ds.files, SuiteFile{
		Path:    path,
		BaseDir: ds.RootDir,
		Ext:     ext,
		Ignored: isXSuite,
	})

	return nil
}

// HasNext returns true in case there is at least one more suite.
func (ds *DirSuiteFileIterator) HasNext() bool {
	return len(ds.files) > ds.pos
}

// Next returns next deserialized suite.
func (ds *DirSuiteFileIterator) Next() *SuiteFile {
	if len(ds.files) <= ds.pos {
		return nil
	}

	file := ds.files[ds.pos]
	ds.pos = ds.pos + 1
	return &file
}

// NewSuiteLoader returns channel of suites that are read from specified folder.
func NewSuiteLoader(rootDir, suiteExt, xsuiteExt string) <-chan TestSuite {
	channel := make(chan TestSuite)

	source := &DirSuiteFileIterator{RootDir: rootDir, SuiteExt: suiteExt, XSuiteExt: xsuiteExt}
	source.init()

	go func() {
		for source.HasNext() {
			sf := source.Next()
			if sf == nil {
				continue
			}
			suite := sf.ToSuite()
			if suite == nil {
				continue
			}

			channel <- *suite
		}

		close(channel)
	}()

	return channel
}

// ValidateSuites detects syntax errors in all test suites in the root directory.
func ValidateSuites(rootDir, suiteExt, xsuiteExt string) error {
	source := &DirSuiteFileIterator{RootDir: rootDir, SuiteExt: suiteExt, XSuiteExt: xsuiteExt}
	source.init()

	errs := make([]*SuiteFileError, 0)

	for source.HasNext() {
		sf := source.Next()

		if sf == nil {
			continue
		}

		err := validateSuite(sf.Path)
		if err != nil {
			errs = append(errs, &SuiteFileError{SuiteFile: sf, err: err})
		}
	}

	if len(errs) == 0 {
		return nil
	}

	return SuitesValidationError{errors: errs}
}

// SuiteFileError desctibes issue during loading of single test suite file
type SuiteFileError struct {
	SuiteFile *SuiteFile
	err       error
}

func (e SuiteFileError) Error() string {
	if e.err == nil {
		return ""
	}

	return fmt.Sprintf("%s: %s", e.SuiteFile.Path, e.err.Error())
}

// SuitesValidationError groups all errors for individual test suites
type SuitesValidationError struct {
	errors []*SuiteFileError
}

func (e SuitesValidationError) Error() string {
	msg := make([]string, 0)
	for _, err := range e.errors {
		msg = append(msg, err.Error())
	}

	return strings.Join(msg, "\n")
}

func isSuite(path string) bool {
	schemaLoader := gojsonschema.NewStringLoader(suiteShapeSchema)

	path, _ = filepath.Abs(path)
	documentLoader := gojsonschema.NewReferenceLoader("file:///" + path)

	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
	if err != nil {
		return false
	}

	return result.Valid()
}

func validateSuite(path string) error {
	schemaLoader := gojsonschema.NewStringLoader(suiteDetailedSchema)

	path, _ = filepath.Abs(path)
	documentLoader := gojsonschema.NewReferenceLoader("file:///" + filepath.ToSlash(path))

	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
	if err != nil {
		return err
	}

	if !result.Valid() {
		msg := make([]string, 0)
		for _, desc := range result.Errors() {
			msg = append(msg, fmt.Sprintf("Field: %s, Error: %s", desc.Field(), desc.Description()))
		}

		return errors.New(strings.Join(msg, "\n"))
	}

	return nil
}

// used to detect suite
const suiteShapeSchema = `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string"
      },
      "calls": {
        "type": "array"
      }
    },
    "required": [
      "name",
      "calls"
    ]
  }
}
`

// used to validate suite
const suiteDetailedSchema = `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string"
      },
      "description": {
        "type": "string"
      },
      "ignore": {
        "type": "string",
        "minLength": 10
      },
      "calls": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "args": {
              "type": "object",
              "minProperties": 1
            },
            "on": {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "method": {
                  "type": "string",
                  "enum": [
                    "GET",
                    "POST",
                    "PUT",
                    "DELETE",
                    "HEAD",
                    "OPTIONS",
                    "PATCH",
                    "CONNECT",
                    "TRACE"
                  ]
                },
                "url": {
                  "type": "string"
                },
                "headers": {
                  "type": "object",
                  "minProperties": 1
                },
                "params": {
                  "type": "object",
                  "minProperties": 1
                },
                "body": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "object"
                    }
                  ]
                },
                "bodyFile": {
                  "type": "string"
                }
              },
              "required": [
                "method",
                "url"
              ],
              "additionalProperties": false
            },
            "expect": {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "statusCode": {
                  "type": "integer"
                },
                "contentType": {
                  "type": "string"
                },
                "headers": {
                  "type": "object",
                  "minProperties": 1
                },
								"body": {
									"type": "object",
													"minProperties": 1
								},
								"exactBody": {
									"type": "object",
													"minProperties": 1
								},
								"bodyPath": {
									"type": "object",
													"minProperties": 1
												},
								"bodySchema": {
									"type": "object"
								},
                "bodySchemaFile": {
                  "type": "string"
                },
                "bodySchemaURI": {
                  "type": "string"
                },
                "absent": {
                  "type": "array",
                  "minItems": 1
                }
              },
              "additionalProperties": false
            },
            "remember": {
              "type": "object",
              "minProperties": 1,
              "properties": {
                "body": {
                  "type": "object",
                  "minProperties": 1
                },
                "headers": {
                  "type": "object",
                  "minProperties": 1
                }
              },
              "additionalProperties": false
            }
          },
          "required": [
            "on",
            "expect"
					],
					"additionalProperties": false
        }
      }
    },
    "required": [
      "calls"
    ]
  }
}
`
<|endoftext|>


<commit_before>package soft

import (
	"bufio"
	"os"
	"strconv"
	"strings"
)

type plyFormat int

const (
	_ plyFormat = iota
	plyAscii
	plyBinaryLittleEndian
	plyBinaryBigEndian
)

var plyFormatMapping = map[string]plyFormat{
	"ascii":                plyAscii,
	"binary_little_endian": plyBinaryLittleEndian,
	"binary_big_endian":    plyBinaryBigEndian,
}

type plyDataType int

const (
	plyNone plyDataType = iota
	plyInt8
	plyUint8
	plyInt16
	plyUint16
	plyInt32
	plyUint32
	plyFloat32
	plyFloat64
)

var plyDataTypeMapping = map[string]plyDataType{
	"char":    plyInt8,
	"uchar":   plyUint8,
	"short":   plyInt16,
	"ushort":  plyUint16,
	"int":     plyInt32,
	"uint":    plyUint32,
	"float":   plyFloat32,
	"double":  plyFloat64,
	"int8":    plyInt8,
	"uint8":   plyUint8,
	"int16":   plyInt16,
	"uint16":  plyUint16,
	"int32":   plyInt32,
	"uint32":  plyUint32,
	"float32": plyFloat32,
	"float64": plyFloat64,
}

type plyProperty struct {
	name      string
	countType plyDataType
	dataType  plyDataType
}

type plyElement struct {
	name       string
	count      int
	properties []plyProperty
}

func LoadPLY(path string) (*Mesh, error) {
	// open file
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// read lines
	scanner := bufio.NewScanner(file)
	var element plyElement
	var elements []plyElement
	format := plyAscii
	bytes := 0
	for scanner.Scan() {
		line := scanner.Text()
		bytes += len(line) + 1
		f := strings.Fields(line)
		if len(f) == 0 {
			continue
		}
		if f[0] == "format" {
			format = plyFormatMapping[f[1]]
		}
		if f[0] == "element" {
			if element.count > 0 {
				elements = append(elements, element)
			}
			name := f[1]
			count, _ := strconv.ParseInt(f[2], 0, 0)
			element = plyElement{name, int(count), nil}
		}
		if f[0] == "property" {
			if f[1] == "list" {
				countType := plyDataTypeMapping[f[2]]
				dataType := plyDataTypeMapping[f[3]]
				name := f[4]
				property := plyProperty{name, countType, dataType}
				element.properties = append(element.properties, property)
			} else {
				countType := plyNone
				dataType := plyDataTypeMapping[f[1]]
				name := f[2]
				property := plyProperty{name, countType, dataType}
				element.properties = append(element.properties, property)
			}
		}
		if f[0] == "end_header" {
			if element.count > 0 {
				elements = append(elements, element)
			}
			break
		}
	}

	// check for errors
	if err = scanner.Err(); err != nil {
		return nil, err
	}

	file.Seek(int64(bytes), 0)

	if format != plyAscii {
		panic("only ascii ply files are supported")
	}

	return readFormatAscii(file, elements)
}

func readFormatAscii(file *os.File, elements []plyElement) (*Mesh, error) {
	scanner := bufio.NewScanner(file)
	var vertexes []Vector
	var triangles []*Triangle
	for _, element := range elements {
		for i := 0; i < element.count; i++ {
			scanner.Scan()
			line := scanner.Text()
			f := strings.Fields(line)
			fi := 0
			vertex := Vector{}
			for _, property := range element.properties {
				if property.name == "x" {
					vertex.X, _ = strconv.ParseFloat(f[fi], 64)
				}
				if property.name == "y" {
					vertex.Y, _ = strconv.ParseFloat(f[fi], 64)
				}
				if property.name == "z" {
					vertex.Z, _ = strconv.ParseFloat(f[fi], 64)
				}
				if property.name == "vertex_indices" {
					i1, _ := strconv.ParseInt(f[fi+1], 0, 0)
					i2, _ := strconv.ParseInt(f[fi+2], 0, 0)
					i3, _ := strconv.ParseInt(f[fi+3], 0, 0)
					t := Triangle{}
					t.V1.Position = vertexes[i1]
					t.V2.Position = vertexes[i2]
					t.V3.Position = vertexes[i3]
					triangles = append(triangles, &t)
					fi += 3
				}
				fi++
			}
			if element.name == "vertex" {
				vertexes = append(vertexes, vertex)
			}
		}
	}
	return NewMesh(triangles), nil
}
<commit_msg>ply format support<commit_after>package soft

import (
	"bufio"
	"encoding/binary"
	"os"
	"strconv"
	"strings"
)

type plyFormat int

const (
	_ plyFormat = iota
	plyAscii
	plyBinaryLittleEndian
	plyBinaryBigEndian
)

var plyFormatMapping = map[string]plyFormat{
	"ascii":                plyAscii,
	"binary_little_endian": plyBinaryLittleEndian,
	"binary_big_endian":    plyBinaryBigEndian,
}

type plyDataType int

const (
	plyNone plyDataType = iota
	plyInt8
	plyUint8
	plyInt16
	plyUint16
	plyInt32
	plyUint32
	plyFloat32
	plyFloat64
)

var plyDataTypeMapping = map[string]plyDataType{
	"char":    plyInt8,
	"uchar":   plyUint8,
	"short":   plyInt16,
	"ushort":  plyUint16,
	"int":     plyInt32,
	"uint":    plyUint32,
	"float":   plyFloat32,
	"double":  plyFloat64,
	"int8":    plyInt8,
	"uint8":   plyUint8,
	"int16":   plyInt16,
	"uint16":  plyUint16,
	"int32":   plyInt32,
	"uint32":  plyUint32,
	"float32": plyFloat32,
	"float64": plyFloat64,
}

type plyProperty struct {
	name      string
	countType plyDataType
	dataType  plyDataType
}

type plyElement struct {
	name       string
	count      int
	properties []plyProperty
}

func LoadPLY(path string) (*Mesh, error) {
	// open file
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// read header
	reader := bufio.NewReader(file)
	var element plyElement
	var elements []plyElement
	format := plyAscii
	bytes := 0
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			return nil, err
		}
		bytes += len(line)
		f := strings.Fields(line)
		if len(f) == 0 {
			continue
		}
		if f[0] == "format" {
			format = plyFormatMapping[f[1]]
		}
		if f[0] == "element" {
			if element.count > 0 {
				elements = append(elements, element)
			}
			name := f[1]
			count, _ := strconv.ParseInt(f[2], 0, 0)
			element = plyElement{name, int(count), nil}
		}
		if f[0] == "property" {
			if f[1] == "list" {
				countType := plyDataTypeMapping[f[2]]
				dataType := plyDataTypeMapping[f[3]]
				name := f[4]
				property := plyProperty{name, countType, dataType}
				element.properties = append(element.properties, property)
			} else {
				countType := plyNone
				dataType := plyDataTypeMapping[f[1]]
				name := f[2]
				property := plyProperty{name, countType, dataType}
				element.properties = append(element.properties, property)
			}
		}
		if f[0] == "end_header" {
			if element.count > 0 {
				elements = append(elements, element)
			}
			break
		}
	}

	file.Seek(int64(bytes), 0)

	switch format {
	case plyBinaryBigEndian:
		return loadPlyBinary(file, elements, binary.BigEndian)
	case plyBinaryLittleEndian:
		return loadPlyBinary(file, elements, binary.LittleEndian)
	default:
		return loadPlyAscii(file, elements)
	}
}

func loadPlyAscii(file *os.File, elements []plyElement) (*Mesh, error) {
	scanner := bufio.NewScanner(file)
	var vertexes []Vector
	var triangles []*Triangle
	for _, element := range elements {
		for i := 0; i < element.count; i++ {
			scanner.Scan()
			line := scanner.Text()
			f := strings.Fields(line)
			fi := 0
			vertex := Vector{}
			for _, property := range element.properties {
				if property.name == "x" {
					vertex.X, _ = strconv.ParseFloat(f[fi], 64)
				}
				if property.name == "y" {
					vertex.Y, _ = strconv.ParseFloat(f[fi], 64)
				}
				if property.name == "z" {
					vertex.Z, _ = strconv.ParseFloat(f[fi], 64)
				}
				if property.name == "vertex_indices" {
					i1, _ := strconv.ParseInt(f[fi+1], 0, 0)
					i2, _ := strconv.ParseInt(f[fi+2], 0, 0)
					i3, _ := strconv.ParseInt(f[fi+3], 0, 0)
					t := Triangle{}
					t.V1.Position = vertexes[i1]
					t.V2.Position = vertexes[i2]
					t.V3.Position = vertexes[i3]
					t.FixNormals()
					triangles = append(triangles, &t)
					fi += 3
				}
				fi++
			}
			if element.name == "vertex" {
				vertexes = append(vertexes, vertex)
			}
		}
	}
	return NewMesh(triangles), nil
}

func loadPlyBinary(file *os.File, elements []plyElement, order binary.ByteOrder) (*Mesh, error) {
	var vertexes []Vector
	var triangles []*Triangle
	for _, element := range elements {
		for i := 0; i < element.count; i++ {
			var vertex Vector
			var points []Vector
			for _, property := range element.properties {
				if property.countType == plyNone {
					value, err := readPlyFloat(file, order, property.dataType)
					if err != nil {
						return nil, err
					}
					if property.name == "x" {
						vertex.X = value
					}
					if property.name == "y" {
						vertex.Y = value
					}
					if property.name == "z" {
						vertex.Z = value
					}
				} else {
					count, err := readPlyInt(file, order, property.countType)
					if err != nil {
						return nil, err
					}
					for j := 0; j < count; j++ {
						value, err := readPlyInt(file, order, property.dataType)
						if err != nil {
							return nil, err
						}
						if property.name == "vertex_indices" {
							points = append(points, vertexes[value])
						}
					}
				}
			}
			if element.name == "vertex" {
				vertexes = append(vertexes, vertex)
			}
			if element.name == "face" {
				t := Triangle{}
				t.V1.Position = points[0]
				t.V2.Position = points[1]
				t.V3.Position = points[2]
				t.FixNormals()
				triangles = append(triangles, &t)
			}
		}
	}
	return NewMesh(triangles), nil
}

func readPlyInt(file *os.File, order binary.ByteOrder, dataType plyDataType) (int, error) {
	value, err := readPlyFloat(file, order, dataType)
	return int(value), err
}

func readPlyFloat(file *os.File, order binary.ByteOrder, dataType plyDataType) (float64, error) {
	switch dataType {
	case plyInt8:
		var value int8
		err := binary.Read(file, order, &value)
		return float64(value), err
	case plyUint8:
		var value uint8
		err := binary.Read(file, order, &value)
		return float64(value), err
	case plyInt16:
		var value int16
		err := binary.Read(file, order, &value)
		return float64(value), err
	case plyUint16:
		var value uint16
		err := binary.Read(file, order, &value)
		return float64(value), err
	case plyInt32:
		var value int32
		err := binary.Read(file, order, &value)
		return float64(value), err
	case plyUint32:
		var value uint32
		err := binary.Read(file, order, &value)
		return float64(value), err
	case plyFloat32:
		var value float32
		err := binary.Read(file, order, &value)
		return float64(value), err
	case plyFloat64:
		var value float64
		err := binary.Read(file, order, &value)
		return float64(value), err
	default:
		return 0, nil
	}
}
<|endoftext|>


<commit_before>package dao

import (
	"encoding/json"
	"time"

	"github.com/boltdb/bolt"
)

type Movie struct {
	Title string `json:"title"`
	Year  int    `json:"year"`
	Ids   struct {
		Trakt int    `json:"trakt"`
		Slug  string `json:"slug"`
		Imdb  string `json:"imdb"`
		Tmdb  int    `json:"tmdb"`
	} `json:"ids"`
	MagnetLink   string    `json:"magnet_link"`
	LastModified time.Time `json:"last_modified"`
}

type MovieStore interface {
	GetMovie(string) (Movie, error)
	AddMovie(Movie) error
	UpdateMovie(Movie) error
	DeleteMovie(string) error
	GetAllMovies() ([]Movie, error)
	GetAllNotFoundMovies() ([]Movie, error)
}

type BoltMovieStore struct {
	db *bolt.DB
}

func InitDB(dbName string) (*BoltMovieStore, error) {
	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltMovieStore{db}, nil
}

func (store *BoltMovieStore) CreateBucket(bucketName string) error {
	err := store.db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
		return err
	})
	return err
}

func (store *BoltMovieStore) GetMovie(name string) (Movie, error) {
	var movie Movie
	err := store.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		v := b.Get([]byte(name))
		json.Unmarshal(v, &movie)
		return nil
	})
	return movie, err

}

func (store *BoltMovieStore) AddMovie(mov Movie) error {
	err := store.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		if v := b.Get([]byte(mov.Title)); v != nil {
			return nil
		}
		encoded, err := json.Marshal(mov)
		if err != nil {
			return err
		}

		return b.Put([]byte(mov.Title), encoded)
	})
	return err
}

func (store *BoltMovieStore) UpdateMovie(mov Movie) error {
	err := store.db.Update(func(tx *bolt.Tx) error {
		encoded, err := json.Marshal(mov)
		if err != nil {
			return err
		}
		b := tx.Bucket([]byte("movies"))
		return b.Put([]byte(mov.Title), encoded)
	})
	return err
}

func (store *BoltMovieStore) DeleteMovie(name string) error {
	err := store.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		return b.Delete([]byte(name))
	})
	return err
}

func (store *BoltMovieStore) GetAllMovies() ([]Movie, error) {
	var movies []Movie
	err := store.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var movie Movie
			json.Unmarshal(v, &movie)
			movies = append(movies, movie)
		}
		return nil
	})
	return movies, err
}

func (store *BoltMovieStore) GetAllNotFoundMovies() ([]Movie, error) {
	var movies []Movie
	err := store.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var movie Movie
			json.Unmarshal(v, &movie)
			if movie.MagnetLink == "" {
				movies = append(movies, movie)
			}

		}
		return nil
	})
	return movies, err
}
<commit_msg>use trakt id instead of title to store in db<commit_after>package dao

import (
	"encoding/json"
	"strconv"
	"time"

	"github.com/boltdb/bolt"
)

type Movie struct {
	Title string `json:"title"`
	Year  int    `json:"year"`
	Ids   struct {
		Trakt int    `json:"trakt"`
		Slug  string `json:"slug"`
		Imdb  string `json:"imdb"`
		Tmdb  int    `json:"tmdb"`
	} `json:"ids"`
	MagnetLink   string    `json:"magnet_link"`
	LastModified time.Time `json:"last_modified"`
}

type MovieStore interface {
	GetMovie(string) (Movie, error)
	AddMovie(Movie) error
	UpdateMovie(Movie) error
	DeleteMovie(string) error
	GetAllMovies() ([]Movie, error)
	GetAllNotFoundMovies() ([]Movie, error)
}

type BoltMovieStore struct {
	db *bolt.DB
}

func InitDB(dbName string) (*BoltMovieStore, error) {
	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltMovieStore{db}, nil
}

func (store *BoltMovieStore) CreateBucket(bucketName string) error {
	err := store.db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
		return err
	})
	return err
}

func (store *BoltMovieStore) GetMovie(id string) (Movie, error) {
	var movie Movie
	err := store.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		v := b.Get([]byte(id))
		json.Unmarshal(v, &movie)
		return nil
	})
	return movie, err

}

func (store *BoltMovieStore) AddMovie(mov Movie) error {
	err := store.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		if v := b.Get([]byte(strconv.Itoa(mov.Ids.Trakt))); v != nil {
			return nil
		}
		encoded, err := json.Marshal(mov)
		if err != nil {
			return err
		}

		return b.Put([]byte(strconv.Itoa(mov.Ids.Trakt)), encoded)
	})
	return err
}

func (store *BoltMovieStore) UpdateMovie(mov Movie) error {
	err := store.db.Update(func(tx *bolt.Tx) error {
		encoded, err := json.Marshal(mov)
		if err != nil {
			return err
		}
		b := tx.Bucket([]byte("movies"))
		return b.Put([]byte(strconv.Itoa(mov.Ids.Trakt)), encoded)
	})
	return err
}

func (store *BoltMovieStore) DeleteMovie(id string) error {
	err := store.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		return b.Delete([]byte(id))
	})
	return err
}

func (store *BoltMovieStore) GetAllMovies() ([]Movie, error) {
	var movies []Movie
	err := store.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var movie Movie
			json.Unmarshal(v, &movie)
			movies = append(movies, movie)
		}
		return nil
	})
	return movies, err
}

func (store *BoltMovieStore) GetAllNotFoundMovies() ([]Movie, error) {
	var movies []Movie
	err := store.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("movies"))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var movie Movie
			json.Unmarshal(v, &movie)
			if movie.MagnetLink == "" {
				movies = append(movies, movie)
			}

		}
		return nil
	})
	return movies, err
}
<|endoftext|>


<commit_before>package dao

import (
	"encoding/json"

	"time"

	"github.com/boltdb/bolt"
)

type Episode struct {
	Name         string    `json:"name"`
	Code         string    `json:"code"`
	ShowID       int       `json:"show_id"`
	MagnetLink   string    `json:"magnet_link"`
	LastModified time.Time `json:"last_modified"`
}
type Episodes []Episode

type DB struct {
	*bolt.DB
}

func InitDB(dbName string) (*DB, error) {
	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &DB{db}, nil
}

func (db *DB) CreateBucket(bucketName string) error {
	err := db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
		return err
	})
	return err
}

func (db *DB) GetEpisode(name string) (Episode, error) {
	var episode Episode
	err := db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		v := b.Get([]byte(name))
		json.Unmarshal(v, &episode)
		return nil
	})
	return episode, err

}

func (db *DB) AddEpisode(ep Episode) error {
	err := db.Update(func(tx *bolt.Tx) error {
		encoded, err := json.Marshal(ep)
		if err != nil {
			return err
		}
		b := tx.Bucket([]byte("episodes"))
		return b.Put([]byte(ep.Name), encoded)
	})
	return err
}

func (db *DB) DeleteEpisode(name string) error {
	err := db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		return b.Delete([]byte(name))
	})
	return err
}

func (db *DB) GetAllEpisode() ([]Episode, error) {
	var episodes []Episode
	err := db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var episode Episode
			json.Unmarshal(v, &episode)
			episodes = append(episodes, episode)
		}
		return nil
	})
	return episodes, err
}

func (db *DB) GetAllNotFoundEpisode() ([]Episode, error) {
	var episodes []Episode
	err := db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var episode Episode
			json.Unmarshal(v, &episode)
			if episode.MagnetLink == "" {
				episodes = append(episodes, episode)
			}

		}
		return nil
	})
	return episodes, err
}
<commit_msg>Change behavior of AddEpisode<commit_after>package dao

import (
	"encoding/json"

	"time"

	"github.com/boltdb/bolt"
)

type Episode struct {
	Name         string    `json:"name"`
	Code         string    `json:"code"`
	ShowID       int       `json:"show_id"`
	MagnetLink   string    `json:"magnet_link"`
	LastModified time.Time `json:"last_modified"`
}
type Episodes []Episode

type DB struct {
	*bolt.DB
}

func InitDB(dbName string) (*DB, error) {
	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &DB{db}, nil
}

func (db *DB) CreateBucket(bucketName string) error {
	err := db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte(bucketName))
		return err
	})
	return err
}

func (db *DB) GetEpisode(name string) (Episode, error) {
	var episode Episode
	err := db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		v := b.Get([]byte(name))
		json.Unmarshal(v, &episode)
		return nil
	})
	return episode, err

}

func (db *DB) AddEpisode(ep Episode) error {
	err := db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		if v := b.Get([]byte(ep.Name)); v != nil {
			return nil
		}
		encoded, err := json.Marshal(ep)
		if err != nil {
			return err
		}

		return b.Put([]byte(ep.Name), encoded)
	})
	return err
}

func (db *DB) UpdateEpisode(ep Episode) error {
	err := db.Update(func(tx *bolt.Tx) error {
		encoded, err := json.Marshal(ep)
		if err != nil {
			return err
		}
		b := tx.Bucket([]byte("episodes"))
		return b.Put([]byte(ep.Name), encoded)
	})
	return err
}

func (db *DB) DeleteEpisode(name string) error {
	err := db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		return b.Delete([]byte(name))
	})
	return err
}

func (db *DB) GetAllEpisode() ([]Episode, error) {
	var episodes []Episode
	err := db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var episode Episode
			json.Unmarshal(v, &episode)
			episodes = append(episodes, episode)
		}
		return nil
	})
	return episodes, err
}

func (db *DB) GetAllNotFoundEpisode() ([]Episode, error) {
	var episodes []Episode
	err := db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("episodes"))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var episode Episode
			json.Unmarshal(v, &episode)
			if episode.MagnetLink == "" {
				episodes = append(episodes, episode)
			}

		}
		return nil
	})
	return episodes, err
}
<|endoftext|>


<commit_before>package detector

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"qiniupkg.com/x/xlog.v7"
)

// UserAgent Info
var UserAgent = "Observer detector package"

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

// Delivery func after the completion of the request triggering method
type Delivery func(l *xlog.Logger, start, end time.Time, req *http.Request, resp *http.Response, err error)

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

// Client is HTTP Client
type Client struct {
	*http.Client
	Delivery Delivery
}

// DefaultClient is a default HTTP client
var DefaultClient = Client{Client: &http.Client{Transport: DefaultTransport}}

// NewClientTimeout return a timeout HTTP client
func NewClientTimeout(dial, resp time.Duration, delivery Delivery) Client {
	return Client{
		Client:   &http.Client{Transport: NewTransportTimeout(dial, resp)},
		Delivery: delivery,
	}
}

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

// deliver call the delivery method after the http do method execute complete
func (r Client) deliver(l *xlog.Logger, start time.Time, req *http.Request, resp *http.Response, err error) {
	end := time.Now()
	if r.Delivery != nil {
		r.Delivery(l, start, end, req, resp, err)
	}
}

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

// Head send http head request
func (r Client) Head(l *xlog.Logger, url string) (resp *http.Response, err error) {

	req, err := http.NewRequest("HEAD", url, nil)
	if err != nil {
		return
	}
	return r.Do(l, req)
}

// Get send http get request
func (r Client) Get(l *xlog.Logger, url string) (resp *http.Response, err error) {

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return
	}
	return r.Do(l, req)
}

// Delete send http delete request
func (r Client) Delete(l *xlog.Logger, url string) (resp *http.Response, err error) {

	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return
	}
	return r.Do(l, req)
}

// Post send http post request, no Content-Type set
func (r Client) Post(l *xlog.Logger, url string) (resp *http.Response, err error) {

	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		return
	}
	return r.Do(l, req)
}

// PostWith send http post request
func (r Client) PostWith(l *xlog.Logger, url1 string, bodyType string, body io.Reader, bodyLength int) (resp *http.Response, err error) {

	req, err := http.NewRequest("POST", url1, body)
	if err != nil {
		return
	}
	if bodyType != "" {
		req.Header.Set("Content-Type", bodyType)
	}
	req.ContentLength = int64(bodyLength)
	return r.Do(l, req)
}

// PostWithForm send http post form request
func (r Client) PostWithForm(l *xlog.Logger, url1 string, data map[string][]string) (resp *http.Response, err error) {

	msg := url.Values(data).Encode()
	return r.PostWith(l, url1, "application/x-www-form-urlencoded", strings.NewReader(msg), len(msg))
}

// PostWithJSON send http post request, Content-Type: application/json
func (r Client) PostWithJSON(l *xlog.Logger, url1 string, data interface{}) (resp *http.Response, err error) {

	msg, err := json.Marshal(data)
	if err != nil {
		return
	}
	return r.PostWith(l, url1, "application/json", bytes.NewReader(msg), len(msg))
}

// Do the http request
func (r Client) Do(l *xlog.Logger, req *http.Request) (resp *http.Response, err error) {
	start := time.Now()

	if l != nil {
		req.Header.Set("X-Reqid", l.ReqId)
	}

	if req.Header.Get("User-Agent") == "" {
		req.Header.Set("User-Agent", UserAgent)
	}

	resp, err = r.Client.Do(req)

	if err != nil {
		r.deliver(l, start, req, resp, err)
		return
	}

	r.deliver(l, start, req, resp, err)
	return
}
<commit_msg>Remove client PostWithJSON method<commit_after>package detector

import (
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"qiniupkg.com/x/xlog.v7"
)

// UserAgent Info
var UserAgent = "Observer detector package"

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

// Delivery func after the completion of the request triggering method
type Delivery func(l *xlog.Logger, start, end time.Time, req *http.Request, resp *http.Response, err error)

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

// Client is HTTP Client
type Client struct {
	*http.Client
	Delivery Delivery
}

// DefaultClient is a default HTTP client
var DefaultClient = Client{Client: &http.Client{Transport: DefaultTransport}}

// NewClientTimeout return a timeout HTTP client
func NewClientTimeout(dial, resp time.Duration, delivery Delivery) Client {
	return Client{
		Client:   &http.Client{Transport: NewTransportTimeout(dial, resp)},
		Delivery: delivery,
	}
}

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

// deliver call the delivery method after the http do method execute complete
func (r Client) deliver(l *xlog.Logger, start time.Time, req *http.Request, resp *http.Response, err error) {
	end := time.Now()
	if r.Delivery != nil {
		r.Delivery(l, start, end, req, resp, err)
	}
}

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

// Head send http head request
func (r Client) Head(l *xlog.Logger, url string) (resp *http.Response, err error) {

	req, err := http.NewRequest("HEAD", url, nil)
	if err != nil {
		return
	}
	return r.Do(l, req)
}

// Get send http get request
func (r Client) Get(l *xlog.Logger, url string) (resp *http.Response, err error) {

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return
	}
	return r.Do(l, req)
}

// Delete send http delete request
func (r Client) Delete(l *xlog.Logger, url string) (resp *http.Response, err error) {

	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return
	}
	return r.Do(l, req)
}

// Post send http post request, no Content-Type set
func (r Client) Post(l *xlog.Logger, url string) (resp *http.Response, err error) {

	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		return
	}
	return r.Do(l, req)
}

// PostWith send http post request
func (r Client) PostWith(l *xlog.Logger, url1 string, bodyType string, body io.Reader, bodyLength int) (resp *http.Response, err error) {

	req, err := http.NewRequest("POST", url1, body)
	if err != nil {
		return
	}
	if bodyType != "" {
		req.Header.Set("Content-Type", bodyType)
	}
	req.ContentLength = int64(bodyLength)
	return r.Do(l, req)
}

// PostWithForm send http post form request
func (r Client) PostWithForm(l *xlog.Logger, url1 string, data map[string][]string) (resp *http.Response, err error) {

	msg := url.Values(data).Encode()
	return r.PostWith(l, url1, "application/x-www-form-urlencoded", strings.NewReader(msg), len(msg))
}

// Do the http request
func (r Client) Do(l *xlog.Logger, req *http.Request) (resp *http.Response, err error) {
	start := time.Now()

	if l != nil {
		req.Header.Set("X-Reqid", l.ReqId)
	}

	if req.Header.Get("User-Agent") == "" {
		req.Header.Set("User-Agent", UserAgent)
	}

	resp, err = r.Client.Do(req)

	if err != nil {
		r.deliver(l, start, req, resp, err)
		return
	}

	r.deliver(l, start, req, resp, err)
	return
}
<|endoftext|>


<commit_before>package remember

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestDbInit(t *testing.T) {
	assert.NotNil(t, Db)
	assert.NotNil(t, Db.DB)
}

func TestDbOriginOPs(t *testing.T) {
	key := []byte("testkey")
	value := []byte("testvalue")

	err := Db.Put(key, value)
	assert.NoError(t, err)

	valueGet, err := Db.Get(key)
	assert.NoError(t, err)
	assert.Equal(t, value, valueGet)

	err = Db.Delete(key)
	assert.NoError(t, err)

	_, err = Db.Get(key)
	assert.Error(t, err)
}
<commit_msg>refactor db test<commit_after>package remember

import (
	"testing"

	"github.com/syndtr/goleveldb/leveldb"

	"github.com/stretchr/testify/assert"
)

func TestDbInit(t *testing.T) {
	assert.NotNil(t, Db)
	assert.NotNil(t, Db.DB)
}

func TestDbOriginOPs(t *testing.T) {
	key := []byte("testkey")
	value := []byte("testvalue")

	err := Db.Put(key, value)
	assert.NoError(t, err)

	valueGet, err := Db.Get(key)
	assert.NoError(t, err)
	assert.Equal(t, value, valueGet)

	err = Db.Delete(key)
	assert.NoError(t, err)

	_, err = Db.Get(key)
	assert.Equal(t, err, leveldb.ErrNotFound)
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Janoš Guljaš <janos@resenje.org>
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found s the LICENSE file.

// Package casbab is a Go library for converting
// representation style of compound words or phrases.
// Different writing styles of compound words are used
// for different purposes in computer code and variables
// to easily distinguish type, properties or meaning.
//
// Functions in this package are separating words from
// input string and constructing an appropriate phrase
// representation.
//
// Examples:
//     Kebab("camel_snake_kebab") == "camel-snake-kebab"
//     ScreamingSnake("camel_snake_kebab") == "__CAMEL_SNAKE_KEBAB__"
//     Camel("camel_snake_kebab") == "camelSnakeKebab"
//     Pascal("camel_snake_kebab") == "CamelSnakeKebab"
//     Snake("camelSNAKEKebab") == "camel_snake_kebab"
//
// Word separation works by detecting delimiters hyphen (-),
// underscore (_), space ( ) and letter case change.
//
// Note: Leading and trailing separators will be preserved
// only within the Snake family or within the Kebab family
// and not across them. This restriction is based on different
// semantics between different writings.
//
// Examples:
//     CamelSnake("__camel_snake_kebab__") == "__Camel_Snake_Kebab__"
//     Kebab("__camel_snake_kebab") == "camel-snake-kebab"
//     Screaming("__camel_snake_kebab") == "CAMEL SNAKE KEBAB"
//     CamelKebab("--camel-snake-kebab") == "--Camel-Snake-Kebab"
//     Snake("--camel-snake-kebab") == "camel_snake_kebab"
//     Screaming("--camel-snake-kebab") == "CAMEL SNAKE KEBAB"
package casbab // import "resenje.org/casbab"

import "strings"

// Camel case is the practice of writing compound words
// or phrases such that each word or abbreviation in the
// middle of the phrase begins with a capital letter,
// with no spaces or hyphens.
//
// Example: "camelSnakeKebab".
func Camel(s string) string {
	return strings.Join(camel(words(s), 1), "")
}

// Pascal case is a variant of Camel case writing where
// the first letter of the first word is always capitalized.
//
// Example: "CamelSnakeKebab".
func Pascal(s string) string {
	return strings.Join(camel(words(s), 0), "")
}

// Snake case is the practice of writing compound words
// or phrases in which the elements are separated with
// one underscore character (_) and no spaces, with all
// element letters lowercased within the compound.
//
// Example: "camel_snake_kebab".
func Snake(s string) string {
	head, tail := headTailCount(s, '_')
	return strings.Repeat("_", head) + strings.Join(words(s), "_") + strings.Repeat("_", tail)
}

// CamelSnake case is a variant of Camel case with
// each element's first letter uppercased.
//
// Example: "Camel_Snake_Kebab".
func CamelSnake(s string) string {
	head, tail := headTailCount(s, '_')
	return strings.Repeat("_", head) + strings.Join(camel(words(s), 0), "_") + strings.Repeat("_", tail)
}

// ScreamingSnake case is a variant of Camel case with
// all letters uppercased.
//
// Example: "CAMEL_SNAKE_KEBAB".
func ScreamingSnake(s string) string {
	head, tail := headTailCount(s, '_')
	return strings.Repeat("_", head) + strings.Join(scream(words(s)), "_") + strings.Repeat("_", tail)
}

// Kebab case is the practice of writing compound words
// or phrases in which the elements are separated with
// one hyphen character (-) and no spaces, with all
// element letters lowercased within the compound.
//
// Example: "camel-snake-kebab".
func Kebab(s string) string {
	head, tail := headTailCount(s, '-')
	return strings.Repeat("-", head) + strings.Join(words(s), "-") + strings.Repeat("-", tail)
}

// CamelKebab case is a variant of Kebab case with
// each element's first letter uppercased.
//
// Example: "Camel-Snake-Kebab".
func CamelKebab(s string) string {
	head, tail := headTailCount(s, '-')
	return strings.Repeat("-", head) + strings.Join(camel(words(s), 0), "-") + strings.Repeat("-", tail)
}

// ScreamingKebab case is a variant of Kebab case with
// all letters uppercased.
//
// Example: "CAMEL-SNAKE-KEBAB".
func ScreamingKebab(s string) string {
	head, tail := headTailCount(s, '-')
	return strings.Repeat("-", head) + strings.Join(scream(words(s)), "-") + strings.Repeat("-", tail)
}

// Lower is returning detected words, not in a compound
// form, but separated by one space character with all
// letters in lower case.
//
// Example: "camel snake kebab".
func Lower(s string) string {
	return strings.Join(words(s), " ")
}

// Title is returning detected words, not in a compound
// form, but separated by one space character with first
// character in all letters in upper case and all other
// letters in lower case.
//
// Example: "Camel Snake Kebab".
func Title(s string) string {
	return strings.Join(camel(words(s), 0), " ")
}

// Screaming is returning detected words, not in a compound
// form, but separated by one space character with all
// letters in upper case.
//
// Example: "CAMEL SNAKE KEBAB".
func Screaming(s string) string {
	return strings.Join(scream(words(s)), " ")
}

func words(s string) (w []string) {
	start := 0
	l := len(s)
	var prevLower, prevUpper bool
Loop:
	for i, c := range s {
		switch c {
		case '-', '_', ' ':
			if start != i {
				w = append(w, strings.ToLower(s[start:i]))
			}
			start = i + 1
			prevLower = false
			prevUpper = false
			continue Loop
		}
		cs := s[i : i+1]
		if strings.ToUpper(cs) == cs {
			prevUpper = true
			if prevLower {
				if i != start {
					w = append(w, strings.ToLower(s[start:i]))
				}
				start = i
				prevLower = false
			}
		} else {
			prevLower = true
			if prevUpper {
				if i-1 != start {
					w = append(w, strings.ToLower(s[start:i-1]))
				}
				start = i - 1
				prevUpper = false
			}
		}
		if i == l-1 {
			w = append(w, strings.ToLower(s[start:]))
		}
	}
	return
}

func scream(s []string) []string {
	for i := 0; i < len(s); i++ {
		s[i] = strings.ToUpper(s[i])
	}
	return s
}

func camel(s []string, start int) []string {
	for i := start; i < len(s); i++ {
		switch len(s[i]) {
		case 0:
		case 1:
			s[i] = strings.ToUpper(s[i][0:1])
		default:
			s[i] = strings.ToUpper(s[i][0:1]) + s[i][1:]
		}
	}
	return s
}

func headTailCount(s string, sub byte) (head, tail int) {
	for i := 0; i < len(s); i++ {
		if s[i] != sub {
			head = i
			break
		}
	}
	for i := len(s) - 1; i >= 0; i-- {
		if s[i] != sub {
			tail = len(s) - i - 1
			break
		}
	}
	return
}
<commit_msg>Fix example in documentation<commit_after>// Copyright (c) 2016, Janoš Guljaš <janos@resenje.org>
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found s the LICENSE file.

// Package casbab is a Go library for converting
// representation style of compound words or phrases.
// Different writing styles of compound words are used
// for different purposes in computer code and variables
// to easily distinguish type, properties or meaning.
//
// Functions in this package are separating words from
// input string and constructing an appropriate phrase
// representation.
//
// Examples:
//     Kebab("camel_snake_kebab") == "camel-snake-kebab"
//     ScreamingSnake("camel_snake_kebab") == "CAMEL_SNAKE_KEBAB"
//     Camel("camel_snake_kebab") == "camelSnakeKebab"
//     Pascal("camel_snake_kebab") == "CamelSnakeKebab"
//     Snake("camelSNAKEKebab") == "camel_snake_kebab"
//
// Word separation works by detecting delimiters hyphen (-),
// underscore (_), space ( ) and letter case change.
//
// Note: Leading and trailing separators will be preserved
// only within the Snake family or within the Kebab family
// and not across them. This restriction is based on different
// semantics between different writings.
//
// Examples:
//     CamelSnake("__camel_snake_kebab__") == "__Camel_Snake_Kebab__"
//     Kebab("__camel_snake_kebab") == "camel-snake-kebab"
//     Screaming("__camel_snake_kebab") == "CAMEL SNAKE KEBAB"
//     CamelKebab("--camel-snake-kebab") == "--Camel-Snake-Kebab"
//     Snake("--camel-snake-kebab") == "camel_snake_kebab"
//     Screaming("--camel-snake-kebab") == "CAMEL SNAKE KEBAB"
package casbab // import "resenje.org/casbab"

import "strings"

// Camel case is the practice of writing compound words
// or phrases such that each word or abbreviation in the
// middle of the phrase begins with a capital letter,
// with no spaces or hyphens.
//
// Example: "camelSnakeKebab".
func Camel(s string) string {
	return strings.Join(camel(words(s), 1), "")
}

// Pascal case is a variant of Camel case writing where
// the first letter of the first word is always capitalized.
//
// Example: "CamelSnakeKebab".
func Pascal(s string) string {
	return strings.Join(camel(words(s), 0), "")
}

// Snake case is the practice of writing compound words
// or phrases in which the elements are separated with
// one underscore character (_) and no spaces, with all
// element letters lowercased within the compound.
//
// Example: "camel_snake_kebab".
func Snake(s string) string {
	head, tail := headTailCount(s, '_')
	return strings.Repeat("_", head) + strings.Join(words(s), "_") + strings.Repeat("_", tail)
}

// CamelSnake case is a variant of Camel case with
// each element's first letter uppercased.
//
// Example: "Camel_Snake_Kebab".
func CamelSnake(s string) string {
	head, tail := headTailCount(s, '_')
	return strings.Repeat("_", head) + strings.Join(camel(words(s), 0), "_") + strings.Repeat("_", tail)
}

// ScreamingSnake case is a variant of Camel case with
// all letters uppercased.
//
// Example: "CAMEL_SNAKE_KEBAB".
func ScreamingSnake(s string) string {
	head, tail := headTailCount(s, '_')
	return strings.Repeat("_", head) + strings.Join(scream(words(s)), "_") + strings.Repeat("_", tail)
}

// Kebab case is the practice of writing compound words
// or phrases in which the elements are separated with
// one hyphen character (-) and no spaces, with all
// element letters lowercased within the compound.
//
// Example: "camel-snake-kebab".
func Kebab(s string) string {
	head, tail := headTailCount(s, '-')
	return strings.Repeat("-", head) + strings.Join(words(s), "-") + strings.Repeat("-", tail)
}

// CamelKebab case is a variant of Kebab case with
// each element's first letter uppercased.
//
// Example: "Camel-Snake-Kebab".
func CamelKebab(s string) string {
	head, tail := headTailCount(s, '-')
	return strings.Repeat("-", head) + strings.Join(camel(words(s), 0), "-") + strings.Repeat("-", tail)
}

// ScreamingKebab case is a variant of Kebab case with
// all letters uppercased.
//
// Example: "CAMEL-SNAKE-KEBAB".
func ScreamingKebab(s string) string {
	head, tail := headTailCount(s, '-')
	return strings.Repeat("-", head) + strings.Join(scream(words(s)), "-") + strings.Repeat("-", tail)
}

// Lower is returning detected words, not in a compound
// form, but separated by one space character with all
// letters in lower case.
//
// Example: "camel snake kebab".
func Lower(s string) string {
	return strings.Join(words(s), " ")
}

// Title is returning detected words, not in a compound
// form, but separated by one space character with first
// character in all letters in upper case and all other
// letters in lower case.
//
// Example: "Camel Snake Kebab".
func Title(s string) string {
	return strings.Join(camel(words(s), 0), " ")
}

// Screaming is returning detected words, not in a compound
// form, but separated by one space character with all
// letters in upper case.
//
// Example: "CAMEL SNAKE KEBAB".
func Screaming(s string) string {
	return strings.Join(scream(words(s)), " ")
}

func words(s string) (w []string) {
	start := 0
	l := len(s)
	var prevLower, prevUpper bool
Loop:
	for i, c := range s {
		switch c {
		case '-', '_', ' ':
			if start != i {
				w = append(w, strings.ToLower(s[start:i]))
			}
			start = i + 1
			prevLower = false
			prevUpper = false
			continue Loop
		}
		cs := s[i : i+1]
		if strings.ToUpper(cs) == cs {
			prevUpper = true
			if prevLower {
				if i != start {
					w = append(w, strings.ToLower(s[start:i]))
				}
				start = i
				prevLower = false
			}
		} else {
			prevLower = true
			if prevUpper {
				if i-1 != start {
					w = append(w, strings.ToLower(s[start:i-1]))
				}
				start = i - 1
				prevUpper = false
			}
		}
		if i == l-1 {
			w = append(w, strings.ToLower(s[start:]))
		}
	}
	return
}

func scream(s []string) []string {
	for i := 0; i < len(s); i++ {
		s[i] = strings.ToUpper(s[i])
	}
	return s
}

func camel(s []string, start int) []string {
	for i := start; i < len(s); i++ {
		switch len(s[i]) {
		case 0:
		case 1:
			s[i] = strings.ToUpper(s[i][0:1])
		default:
			s[i] = strings.ToUpper(s[i][0:1]) + s[i][1:]
		}
	}
	return s
}

func headTailCount(s string, sub byte) (head, tail int) {
	for i := 0; i < len(s); i++ {
		if s[i] != sub {
			head = i
			break
		}
	}
	for i := len(s) - 1; i >= 0; i-- {
		if s[i] != sub {
			tail = len(s) - i - 1
			break
		}
	}
	return
}
<|endoftext|>


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

package parts

import (
	"fmt"

	"github.com/google/shenzhen-go/dev/model"
	"github.com/google/shenzhen-go/dev/model/pin"
)

const cacheKeyTypeParam = "$Key"

func cachePutType(kt string) string {
	return fmt.Sprintf("struct{ Key %s; Data []byte }", kt)
}

var (
	cachePins = pin.NewMap(
		&pin.Definition{
			Name:      "get",
			Direction: pin.Input,
			Type:      cacheKeyTypeParam,
		},
		&pin.Definition{
			Name:      "put",
			Direction: pin.Input,
			Type:      cachePutType(cacheKeyTypeParam),
		},
		&pin.Definition{
			Name:      "hit",
			Direction: pin.Output,
			Type:      cachePutType(cacheKeyTypeParam),
		},
		&pin.Definition{
			Name:      "miss",
			Direction: pin.Output,
			Type:      cacheKeyTypeParam,
		},
	)
)

func init() {
	model.RegisterPartType("Cache", "Flow", &model.PartType{
		New: func() model.Part {
			return &Cache{
				ContentBytesLimit: 1 << 30,
				EvictionMode:      EvictLRU,
			}
		},
		Panels: []model.PartPanel{{
			Name: "Help",
			Editor: `

				A Cache part caches content in memory.

				TODO: Implement Cache part.

`,
		}},
	})
}

// Cache is a part which caches content in memory.
type Cache struct {
	ContentBytesLimit int64
	EvictionMode      CacheEvictionMode
}

// CacheEvictionMode is how the cache decides which content to evict
// to stay under the memory limit.
type CacheEvictionMode string

// Cache eviction modes.
const (
	EvictLRU CacheEvictionMode = "lru" // Least recently used
	EvictMRU CacheEvictionMode = "mru" // Most recently used
)

func (m CacheEvictionMode) searchParams() (init, comp string) {
	switch m {
	case EvictLRU:
		return "time.Now()", "Before"
	case EvictMRU:
		return "time.Time{}", "After"
	default:
		panic("unrecognised EvictionMode " + m)
	}
}

// Clone returns a clone of this Cache.
func (c *Cache) Clone() model.Part {
	c0 := *c
	return &c0
}

// Impl returns a cache implementation.
func (c *Cache) Impl(types map[string]string) (head, body, tail string) {
	keyType := types[cacheKeyTypeParam]
	putType := cachePutType(keyType)
	initTime, timeComp := c.EvictionMode.searchParams()
	return fmt.Sprintf(`
		const bytesLimit = %d
		type cacheEntry struct {
			data []byte
			last time.Time
		}
		var mu sync.Mutex
		totalBytes := int64(0)
		cache := make(map[%s]cacheEntry)
	`, c.ContentBytesLimit, keyType),
		fmt.Sprintf(`for {
			select {
			case g := <-get:
				mu.RLock()
				if e, ok := cache[g]; !ok {
					miss <- g
					mu.RUnlock()
					continue
				}
				hit <- %s{
					Key: g,
					Data: e.data,
				}
				mu.RUnlock()
				mu.Lock()
				cache[g] = cacheEntry{
					data: e.data,
					last: time.Now(),
				}
				mu.Unlock()

			case p := <-put:
				if len(p.Data) > bytesLimit {
					// TODO: some kind of failure message
					continue
				}
				mu.Lock()
				// TODO: Can improve eviction algorithm - this is simplistic but O(n^2)
				for totalBytes + len(p.Data) > bytesLimit {
					et := %s
					var ek %s
					for k, e := range cache {
						if e.last.%s(et) {
							et, ek = e.last, k
						}
					}
					totalBytes -= len(cache[k].data)
					delete(cache, k)
				}
				cache[p.Key] = cacheEntry{
					data: p.Data,
					last: time.Now(),
				}
				totalBytes += len(p.Data)
				mu.Unlock()
			}
		}`, putType, initTime, keyType, timeComp),
		`close(hit)
		close(miss)`
}

// Imports returns nil.
func (c *Cache) Imports() []string {
	return []string{`"sync"`, `"time"`}
}

// Pins returns a pin map.
func (c *Cache) Pins() pin.Map {
	return cachePins
}

// TypeKey returns "Cache".
func (c *Cache) TypeKey() string {
	return "Cache"
}
<commit_msg>More tweaks to Cache while it's still unused<commit_after>// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package parts

import (
	"fmt"

	"github.com/google/shenzhen-go/dev/model"
	"github.com/google/shenzhen-go/dev/model/pin"
)

const cacheKeyTypeParam = "$Key"

func cachePutType(kt string) string {
	return fmt.Sprintf("struct{ Key %s; Data []byte }", kt)
}

var (
	cachePins = pin.NewMap(
		&pin.Definition{
			Name:      "get",
			Direction: pin.Input,
			Type:      cacheKeyTypeParam,
		},
		&pin.Definition{
			Name:      "put",
			Direction: pin.Input,
			Type:      cachePutType(cacheKeyTypeParam),
		},
		&pin.Definition{
			Name:      "hit",
			Direction: pin.Output,
			Type:      cachePutType(cacheKeyTypeParam),
		},
		&pin.Definition{
			Name:      "miss",
			Direction: pin.Output,
			Type:      cacheKeyTypeParam,
		},
	)
)

func init() {
	model.RegisterPartType("Cache", "Flow", &model.PartType{
		New: func() model.Part {
			return &Cache{
				ContentBytesLimit: 1 << 30,
				EvictionMode:      EvictLRU,
			}
		},
		Panels: []model.PartPanel{{
			Name: "Help",
			Editor: `

				A Cache part caches content in memory.

				TODO: Implement Cache part.

`,
		}},
	})
}

// Cache is a part which caches content in memory.
type Cache struct {
	ContentBytesLimit int64
	EvictionMode      CacheEvictionMode
}

// CacheEvictionMode is how the cache decides which content to evict
// to stay under the memory limit.
type CacheEvictionMode string

// Cache eviction modes.
const (
	EvictLRU CacheEvictionMode = "lru" // Least recently used
	EvictMRU CacheEvictionMode = "mru" // Most recently used
)

func (m CacheEvictionMode) searchParams() (init, comp string) {
	switch m {
	case EvictLRU:
		return "time.Now()", "Before"
	case EvictMRU:
		return "time.Time{}", "After"
	default:
		panic("unrecognised EvictionMode " + m)
	}
}

// Clone returns a clone of this Cache.
func (c *Cache) Clone() model.Part {
	c0 := *c
	return &c0
}

// Impl returns a cache implementation.
func (c *Cache) Impl(types map[string]string) (head, body, tail string) {
	keyType := types[cacheKeyTypeParam]
	putType := cachePutType(keyType)
	initTime, timeComp := c.EvictionMode.searchParams()
	return fmt.Sprintf(`
		const bytesLimit = %d
		type cacheEntry struct {
			data []byte
			last time.Time
			mu   sync.Mutex
		}
		var mu sync.RWMutex
		totalBytes := int64(0)
		cache := make(map[%s]*cacheEntry)
	`, c.ContentBytesLimit, keyType),
		fmt.Sprintf(`selectLoop:
		for {
			select {
			case g := <-get:
				mu.RLock()
				e, ok := cache[g]
				if !ok {
					miss <- g
					mu.RUnlock()
					continue selectLoop
				}
				mu.RUnlock()
				e.mu.Lock()
				hit <- %s{
					Key: g,
					Data: e.data,
				}
				e.last = time.Now()
				e.mu.Unlock()

			case p := <-put:
				if len(p.Data) > bytesLimit {
					// TODO: some kind of failure message
					continue
				}
				mu.RLock()
				// TODO: Can improve eviction algorithm - this is simplistic but O(n^2)
				for totalBytes + len(p.Data) > bytesLimit {
					et := %s
					var ek %s
					for k, e := range cache {
						if e.last.%s(et) {
							et, ek = e.last, k
						}
					}
					mu.RUnlock()
					mu.Lock()
					// Check it's still necessary.
					if totalBytes + len(p.Data) <= bytesLimit {
						mu.Unlock()
						continue selectLoop
					}
					totalBytes -= len(cache[k].data)
					delete(cache, k)
					mu.Unlock()
					mu.RLock()
				}
				mu.RUnlock()
				mu.Lock()
				cache[p.Key] = &cacheEntry{
					data: p.Data,
					last: time.Now(),
				}
				totalBytes += len(p.Data)
				mu.Unlock()
			}
		}`, putType, initTime, keyType, timeComp),
		`close(hit)
		close(miss)`
}

// Imports returns nil.
func (c *Cache) Imports() []string {
	return []string{`"sync"`, `"time"`}
}

// Pins returns a pin map.
func (c *Cache) Pins() pin.Map {
	return cachePins
}

// TypeKey returns "Cache".
func (c *Cache) TypeKey() string {
	return "Cache"
}
<|endoftext|>