<commit_before>// Copyright © 2016 Jip J. Dekker <jip@dekker.li>
//
// 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 compiler

import (
	"os"
	"path/filepath"

	log "github.com/Sirupsen/logrus"
	"github.com/jjdekker/ponder/settings"
)

var (
	scores []*settings.Score
)

// CompileDir compiles all lilypond files and makes all
// sheet music available in the OutputDir
func CompileDir(path string, opts *settings.Settings) {
	// Find all scores
	collector := generateScores()
	filepath.Walk(path, compilePath(path, opts, collector))

	PrepareLilypond(opts)
	for _, score := range scores {
		// TODO: compile only if there are changes
		msg, err := Lilypond(score.Path)
		if err != nil {
			log.WithFields(log.Fields{
				"message": msg,
				"error":   err,
				"score":   score,
			}).Warning("score failed to compile")
		}
	}
}

func generateScores() func(string, os.FileInfo) error {
	return func(path string, file os.FileInfo) error {
		switch filepath.Ext(path) {
		case ".ly":
			log.WithFields(log.Fields{"path": path}).Info("adding lilypond file")
			scores = append(scores, &settings.Score{
				Path:         path,
				LastModified: file.ModTime(),
			})

		case ".json":
			if filepath.Base(path) != "ponder.json" {
				log.WithFields(log.Fields{"path": path}).Info("adding json file")
				if score, err := settings.FromJSON(path); err != nil {
					log.WithFields(log.Fields{
						"error": err,
						"path":  path,
					}).Warning("unable to parse score settings, skipping...")
				} else {
					scores = append(scores, score)
				}
			}

		default:
			log.WithFields(log.Fields{"path": path}).Debug("ignoring file")
		}
		return nil
	}
}
<commit_msg>Adds conditional compilations, depending on when the source was last modified<commit_after>// Copyright © 2016 Jip J. Dekker <jip@dekker.li>
//
// 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 compiler

import (
	"os"
	"path/filepath"

	log "github.com/Sirupsen/logrus"
	"github.com/jjdekker/ponder/helpers"
	"github.com/jjdekker/ponder/settings"
)

var (
	scores []*settings.Score
)

// CompileDir compiles all lilypond files and makes all
// sheet music available in the OutputDir
func CompileDir(path string, opts *settings.Settings) {
	// Find all scores
	collector := generateScores()
	filepath.Walk(path, compilePath(path, opts, collector))

	PrepareLilypond(opts)
	for _, score := range scores {
		output := outputPath(score.Path, opts)

		if !helpers.Exists(output) ||
			score.LastModified.After(helpers.LastModified(output)) {
			msg, err := Lilypond(score.Path)
			if err != nil {
				log.WithFields(log.Fields{
					"message": msg,
					"error":   err,
					"score":   score,
				}).Warning("score failed to compile")
			}
		}
	}
}

func generateScores() func(string, os.FileInfo) error {
	return func(path string, file os.FileInfo) error {
		switch filepath.Ext(path) {
		case ".ly":
			log.WithFields(log.Fields{"path": path}).Info("adding lilypond file")
			scores = append(scores, &settings.Score{
				Path:         path,
				LastModified: file.ModTime(),
			})

		case ".json":
			if filepath.Base(path) != "ponder.json" {
				log.WithFields(log.Fields{"path": path}).Info("adding json file")
				if score, err := settings.FromJSON(path); err != nil {
					log.WithFields(log.Fields{
						"error": err,
						"path":  path,
					}).Warning("unable to parse score settings, skipping...")
				} else {
					scores = append(scores, score)
				}
			}

		default:
			log.WithFields(log.Fields{"path": path}).Debug("ignoring file")
		}
		return nil
	}
}
<|endoftext|>


<commit_before>package ionic

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"testing"

	. "github.com/franela/goblin"
	"github.com/gomicro/bogus"
	"github.com/ion-channel/ionic/dependencies"
	. "github.com/onsi/gomega"
)

func TestDependencies(t *testing.T) {
	g := Goblin(t)
	RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })

	g.Describe("Dependencies", func() {
		var server *bogus.Bogus
		var h, p string
		var client *IonClient

		g.BeforeEach(func() {
			server = bogus.New()
			h, p = server.HostPort()
			client, _ = New(fmt.Sprintf("http://%v:%v", h, p))
		})

		g.It("should get the latest version of a dependency", func() {
			server.AddPath("/v1/dependency/getLatestVersionForDependency").
				SetMethods("GET").
				SetPayload([]byte(sampleLatestVersionResponse)).
				SetStatus(http.StatusOK)
			dep, err := client.GetLatestVersionForDependency("bundler", RubyEcosystem, "atoken")

			Expect(err).To(BeNil())
			Expect(dep.Version).To(Equal("1.16.3"))

			hrs := server.HitRecords()
			Expect(len(hrs)).To(Equal(1))
		})

		g.It("should get the latest version of a dependency", func() {
			server.AddPath("/v1/dependency/getVersionsForDependency").
				SetMethods("GET").
				SetPayload([]byte(sampleLatestVersionsResponse)).
				SetStatus(http.StatusOK)
			deps, err := client.GetVersionsForDependency("bundler", RubyEcosystem, "atoken")

			Expect(err).To(BeNil())
			Expect(deps[0].Version).To(Equal("1.16.3"))

			hrs := server.HitRecords()
			Expect(len(hrs)).To(Equal(1))
		})

		g.It("should resolve dependencies from a definition file", func() {
			server.AddPath("/v1/dependency/resolveDependenciesInFile").
				SetMethods("POST").
				SetPayload([]byte(sampleResolutionResponse)).
				SetStatus(http.StatusOK)

			tf, err := ioutil.TempFile("", "test")
			if err != nil {
				log.Fatal(err)
			}

			defer os.Remove(tf.Name()) // clean up

			tf.Write([]byte(samplePomSnippet))
			tf.Close()

			o := dependencies.DependencyResolutionRequest{
				File:      tf.Name(),
				Flatten:   true,
				Ecosystem: "maven",
			}

			deps, err := client.ResolveDependenciesInFile(o, "atoken")

			Expect(err).To(BeNil())
			Expect(deps.Dependencies[0].Version).To(Equal("1.16.3"))

			hrs := server.HitRecords()
			Expect(len(hrs)).To(Equal(1))
			fmt.Printf("asdfaf:::: %v\n", string(hrs[0].Body))
			Expect(string(hrs[0].Body)).To(ContainSubstring(samplePomSnippet))
		})
	})
}

const (
	sampleLatestVersionResponse  = `{"data":{"version":"1.16.3"}}`
	sampleLatestVersionsResponse = `{"data":["1.16.3","1.16.2"]}`
	sampleResolutionResponse     = `{"data":{"dependencies":[{"version":"1.16.3"}]}}`

	samplePomSnippet = `<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>2.7.3</version>
        </dependency>
    </dependencies>
</project>`
)
<commit_msg>remove debug line<commit_after>package ionic

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"testing"

	. "github.com/franela/goblin"
	"github.com/gomicro/bogus"
	"github.com/ion-channel/ionic/dependencies"
	. "github.com/onsi/gomega"
)

func TestDependencies(t *testing.T) {
	g := Goblin(t)
	RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })

	g.Describe("Dependencies", func() {
		var server *bogus.Bogus
		var h, p string
		var client *IonClient

		g.BeforeEach(func() {
			server = bogus.New()
			h, p = server.HostPort()
			client, _ = New(fmt.Sprintf("http://%v:%v", h, p))
		})

		g.It("should get the latest version of a dependency", func() {
			server.AddPath("/v1/dependency/getLatestVersionForDependency").
				SetMethods("GET").
				SetPayload([]byte(sampleLatestVersionResponse)).
				SetStatus(http.StatusOK)
			dep, err := client.GetLatestVersionForDependency("bundler", RubyEcosystem, "atoken")

			Expect(err).To(BeNil())
			Expect(dep.Version).To(Equal("1.16.3"))

			hrs := server.HitRecords()
			Expect(len(hrs)).To(Equal(1))
		})

		g.It("should get the latest version of a dependency", func() {
			server.AddPath("/v1/dependency/getVersionsForDependency").
				SetMethods("GET").
				SetPayload([]byte(sampleLatestVersionsResponse)).
				SetStatus(http.StatusOK)
			deps, err := client.GetVersionsForDependency("bundler", RubyEcosystem, "atoken")

			Expect(err).To(BeNil())
			Expect(deps[0].Version).To(Equal("1.16.3"))

			hrs := server.HitRecords()
			Expect(len(hrs)).To(Equal(1))
		})

		g.It("should resolve dependencies from a definition file", func() {
			server.AddPath("/v1/dependency/resolveDependenciesInFile").
				SetMethods("POST").
				SetPayload([]byte(sampleResolutionResponse)).
				SetStatus(http.StatusOK)

			tf, err := ioutil.TempFile("", "test")
			if err != nil {
				log.Fatal(err)
			}

			defer os.Remove(tf.Name()) // clean up

			tf.Write([]byte(samplePomSnippet))
			tf.Close()

			o := dependencies.DependencyResolutionRequest{
				File:      tf.Name(),
				Flatten:   true,
				Ecosystem: "maven",
			}

			deps, err := client.ResolveDependenciesInFile(o, "atoken")

			Expect(err).To(BeNil())
			Expect(deps.Dependencies[0].Version).To(Equal("1.16.3"))

			hrs := server.HitRecords()
			Expect(len(hrs)).To(Equal(1))
			Expect(string(hrs[0].Body)).To(ContainSubstring(samplePomSnippet))
		})
	})
}

const (
	sampleLatestVersionResponse  = `{"data":{"version":"1.16.3"}}`
	sampleLatestVersionsResponse = `{"data":["1.16.3","1.16.2"]}`
	sampleResolutionResponse     = `{"data":{"dependencies":[{"version":"1.16.3"}]}}`

	samplePomSnippet = `<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>2.7.3</version>
        </dependency>
    </dependencies>
</project>`
)
<|endoftext|>


<commit_before>package hammer

import (
	"github.com/Sirupsen/logrus"
	"github.com/spf13/viper"
	"io/ioutil"
	"os"
	"path/filepath"
)

type Loader struct {
	Root      string
	Indicator string
}

func NewLoader(root string) *Loader {
	return &Loader{
		Root:      root,
		Indicator: "spec.yml",
	}
}

func (l *Loader) Load() ([]*Package, error) {
	logrus.WithField("root", l.Root).Info("loading packages")
	packages := []*Package{}

	err := filepath.Walk(l.Root, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() || info.Name() != l.Indicator {
			return nil
		}

		logrus.WithField("path", path).Debug("loading package")
		content, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}

		pkg, err := NewPackageFromYAML(content)
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"path":  path,
				"error": err,
			}).Warning("could not load package, skipping")
			return nil
		}
		pkg.Root = path
		pkg.OutputRoot = viper.GetString("output")

		packages = append(packages, pkg)

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

	return packages, nil
}
<commit_msg>loader: fix too specific Root<commit_after>package hammer

import (
	"github.com/Sirupsen/logrus"
	"github.com/spf13/viper"
	"io/ioutil"
	"os"
	"path/filepath"
)

type Loader struct {
	Root      string
	Indicator string
}

func NewLoader(root string) *Loader {
	return &Loader{
		Root:      root,
		Indicator: "spec.yml",
	}
}

func (l *Loader) Load() ([]*Package, error) {
	logrus.WithField("root", l.Root).Info("loading packages")
	packages := []*Package{}

	err := filepath.Walk(l.Root, func(pathName string, info os.FileInfo, err error) error {
		if info.IsDir() || info.Name() != l.Indicator {
			return nil
		}

		logrus.WithField("path", pathName).Debug("loading package")
		content, err := ioutil.ReadFile(pathName)
		if err != nil {
			return err
		}

		pkg, err := NewPackageFromYAML(content)
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"path":  pathName,
				"error": err,
			}).Warning("could not load package, skipping")
			return nil
		}
		path, _ := filepath.Split(pathName)
		pkg.Root = path
		pkg.OutputRoot = viper.GetString("output")

		packages = append(packages, pkg)

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

	return packages, nil
}
<|endoftext|>


<commit_before>package state

import (
	"fmt"
	"math"
	"superstellar/backend/constants"
	"superstellar/backend/pb"
	"superstellar/backend/types"
	"time"
)

// Direction is a type describing user input on spaceship rotation.
type Direction int

// Constants describing user input on spaceship rotation.
const (
	NONE Direction = iota
	RIGHT
	LEFT
)

// Spaceship struct describes a spaceship.
type Spaceship struct {
	ID                      uint32
	Position                *types.Point
	Velocity                *types.Vector
	Facing                  float64
	AngularVelocity         float64
	AngularVelocityDelta    float64
	InputThrust             bool
	InputDirection          Direction
	TargetAngle             *float64
	Fire                    bool
	LastShotTime            time.Time
	Dirty                   bool
	LastSentOn              uint32
	HP                      uint32
	MaxHP                   uint32
	Energy                  uint32
	MaxEnergy               uint32
	AutoRepairDelay         uint32
	AutoEnergyRechargeDelay uint32
}

func NewSpaceship(clientId uint32, initialPosition *types.Point) *Spaceship {
	return &Spaceship{
		ID:              clientId,
		Position:        initialPosition,
		Velocity:        types.ZeroVector(),
		Facing:          0.0,
		AngularVelocity: 0,
		AngularVelocityDelta: 0,
		InputThrust:     false,
		InputDirection:  NONE,
		Fire:            false,
		LastShotTime:    time.Now(),
		Dirty:           true,
		LastSentOn:      0,
		HP:              constants.SpaceshipInitialHP,
		MaxHP:           constants.SpaceshipInitialHP,
		Energy:          constants.SpaceshipInitialEnergy,
		MaxEnergy:       constants.SpaceshipInitialEnergy,
		AutoRepairDelay: constants.AutoRepairDelay,
	}
}

// String function returns string representation.
func (s *Spaceship) String() string {
	return fmt.Sprintf("(%v, %v, %v)", s.Position, s.Velocity, s.Facing)
}

func (s *Spaceship) UpdateUserInput(userInput pb.UserInput) {
	switch userInput {
	case pb.UserInput_CENTER:
		s.InputDirection = NONE
		s.TargetAngle = nil
		s.Dirty = true
	case pb.UserInput_LEFT:
		s.InputDirection = LEFT
		s.TargetAngle = nil
		s.Dirty = true
	case pb.UserInput_RIGHT:
		s.InputDirection = RIGHT
		s.TargetAngle = nil
		s.Dirty = true
	case pb.UserInput_THRUST_ON:
		s.InputThrust = true
		s.Dirty = true
	case pb.UserInput_THRUST_OFF:
		s.InputThrust = false
		s.Dirty = true
	case pb.UserInput_FIRE_START:
		s.Fire = true
	case pb.UserInput_FIRE_STOP:
		s.Fire = false
	}
}

func (s *Spaceship) UpdateTargetAngle(angle float64) {
	s.Dirty = true
	s.TargetAngle = &angle
	s.InputDirection = NONE
}

// ToProto returns protobuf representation
func (s *Spaceship) ToProto() *pb.Spaceship {
	return &pb.Spaceship{
		Id:             s.ID,
		Position:       s.Position.ToProto(),
		Velocity:       s.Velocity.ToProto(),
		Facing:         s.Facing,
		AngularSpeed:   s.AngularVelocity,
		InputDirection: pb.Direction(s.InputDirection),
		InputThrust:    s.InputThrust,
		MaxHp:          s.MaxHP,
		Hp:             s.HP,
		MaxEnergy:      s.MaxEnergy,
		Energy:         s.Energy,
	}
}

// DetectCollision returns true if receiver spaceship collides with other spaceship.
func (s *Spaceship) DetectCollision(other *Spaceship) bool {
	v := types.Point{X: s.Position.X - other.Position.X, Y: s.Position.Y - other.Position.Y}
	dist := v.Length()

	return dist < 2*constants.SpaceshipSize
}

// Collide transforms colliding ships' parameters.
func (s *Spaceship) Collide(other *Spaceship) {
	v := types.Point{
		X: s.Position.X - other.Position.X,
		Y: s.Position.Y - other.Position.Y,
	}

	transformAngle := -math.Atan2(float64(v.Y), float64(v.X))
	newV1 := s.Velocity.Rotate(transformAngle)
	newV2 := other.Velocity.Rotate(transformAngle)

	switchedV1 := types.Vector{X: newV2.X, Y: newV1.Y}
	switchedV2 := types.Vector{X: newV1.X, Y: newV2.Y}

	s.Velocity = switchedV1.Rotate(-transformAngle)
	other.Velocity = switchedV2.Rotate(-transformAngle)

	s.Dirty = true
	other.Dirty = true
}

func (s *Spaceship) ShootIfPossible() (canShoot bool) {
	if s.Energy >= constants.BasicWeaponEnergyCost {
		s.Energy -= constants.BasicWeaponEnergyCost
		canShoot = true
	} else {
		canShoot = false
	}
	return canShoot
}

func (s *Spaceship) CollideWithProjectile(projectile *Projectile) {
	if s.HP < constants.ProjectileDamage {
		s.HP = 0
	} else {
		s.HP -= constants.ProjectileDamage
	}
	s.AutoRepairDelay = constants.AutoRepairDelay
}

func (s *Spaceship) AddReward(reward uint32) {
	s.HP += reward
	s.MaxHP += reward
}

func (s *Spaceship) AddEnergyReward(reward uint32) {
	s.Energy += reward
	s.MaxEnergy += reward
}

func (s *Spaceship) AutoRepair() {
	s.HP += constants.AutoRepairAmount

	if s.HP > s.MaxHP {
		s.HP = s.MaxHP
	}
	s.AutoRepairDelay = constants.AutoRepairInterval
}

func (s *Spaceship) AutoEnergyRecharge() {
	s.Energy += constants.AutoEnergyRechargeAmount

	if s.Energy > s.MaxEnergy {
		s.Energy = s.MaxEnergy
	}
	s.AutoEnergyRechargeDelay = constants.AutoEnergyRechargeInterval
}

func (s *Spaceship) LeftTurn() {
	s.AngularVelocityDelta = s.angularVelocityDelta()
	s.LimitAngularVelocityDelta()
}

func (s *Spaceship) RightTurn() {
	s.AngularVelocityDelta = -s.angularVelocityDelta()
	s.LimitAngularVelocityDelta()
}

func (s *Spaceship) TurnToTarget() {
	targetAngle := *s.TargetAngle
	offset := targetAngle - s.Facing

	if math.Abs(offset) > math.Pi {
		offset -= math.Copysign(2*math.Pi, offset)
	}

	targetAngularVelocity := -offset * constants.SpaceshipTurnToAngleP
	s.AngularVelocityDelta = targetAngularVelocity - s.AngularVelocity

	s.LimitAngularVelocityDelta()
}

func (s *Spaceship) LimitAngularVelocityDelta() {
	potentialAngularVelocity := s.AngularVelocity + s.AngularVelocityDelta;
	diff := math.Abs(potentialAngularVelocity) - constants.SpaceshipMaxAngularVelocity

	if (diff > 0) {
		s.AngularVelocityDelta -= math.Copysign(diff, s.AngularVelocity)
	}
}

func (s *Spaceship) ApplyAngularFriction() {
	s.AngularVelocity *= (1 - constants.SpaceshipAngularFriction)
}

func (s *Spaceship) angularVelocityDelta() float64 {
	nonlinearPart := constants.SpaceshipNonlinearAngularAcceleration * math.Abs(s.AngularVelocity)
	linearPart := constants.SpaceshipLinearAngularAcceleration
	return nonlinearPart + linearPart
}
<commit_msg>Dirty on hit<commit_after>package state

import (
	"fmt"
	"math"
	"superstellar/backend/constants"
	"superstellar/backend/pb"
	"superstellar/backend/types"
	"time"
)

// Direction is a type describing user input on spaceship rotation.
type Direction int

// Constants describing user input on spaceship rotation.
const (
	NONE Direction = iota
	RIGHT
	LEFT
)

// Spaceship struct describes a spaceship.
type Spaceship struct {
	ID                      uint32
	Position                *types.Point
	Velocity                *types.Vector
	Facing                  float64
	AngularVelocity         float64
	AngularVelocityDelta    float64
	InputThrust             bool
	InputDirection          Direction
	TargetAngle             *float64
	Fire                    bool
	LastShotTime            time.Time
	Dirty                   bool
	LastSentOn              uint32
	HP                      uint32
	MaxHP                   uint32
	Energy                  uint32
	MaxEnergy               uint32
	AutoRepairDelay         uint32
	AutoEnergyRechargeDelay uint32
}

func NewSpaceship(clientId uint32, initialPosition *types.Point) *Spaceship {
	return &Spaceship{
		ID:              clientId,
		Position:        initialPosition,
		Velocity:        types.ZeroVector(),
		Facing:          0.0,
		AngularVelocity: 0,
		AngularVelocityDelta: 0,
		InputThrust:     false,
		InputDirection:  NONE,
		Fire:            false,
		LastShotTime:    time.Now(),
		Dirty:           true,
		LastSentOn:      0,
		HP:              constants.SpaceshipInitialHP,
		MaxHP:           constants.SpaceshipInitialHP,
		Energy:          constants.SpaceshipInitialEnergy,
		MaxEnergy:       constants.SpaceshipInitialEnergy,
		AutoRepairDelay: constants.AutoRepairDelay,
	}
}

// String function returns string representation.
func (s *Spaceship) String() string {
	return fmt.Sprintf("(%v, %v, %v)", s.Position, s.Velocity, s.Facing)
}

func (s *Spaceship) UpdateUserInput(userInput pb.UserInput) {
	switch userInput {
	case pb.UserInput_CENTER:
		s.InputDirection = NONE
		s.TargetAngle = nil
		s.Dirty = true
	case pb.UserInput_LEFT:
		s.InputDirection = LEFT
		s.TargetAngle = nil
		s.Dirty = true
	case pb.UserInput_RIGHT:
		s.InputDirection = RIGHT
		s.TargetAngle = nil
		s.Dirty = true
	case pb.UserInput_THRUST_ON:
		s.InputThrust = true
		s.Dirty = true
	case pb.UserInput_THRUST_OFF:
		s.InputThrust = false
		s.Dirty = true
	case pb.UserInput_FIRE_START:
		s.Fire = true
	case pb.UserInput_FIRE_STOP:
		s.Fire = false
	}
}

func (s *Spaceship) UpdateTargetAngle(angle float64) {
	s.Dirty = true
	s.TargetAngle = &angle
	s.InputDirection = NONE
}

// ToProto returns protobuf representation
func (s *Spaceship) ToProto() *pb.Spaceship {
	return &pb.Spaceship{
		Id:             s.ID,
		Position:       s.Position.ToProto(),
		Velocity:       s.Velocity.ToProto(),
		Facing:         s.Facing,
		AngularSpeed:   s.AngularVelocity,
		InputDirection: pb.Direction(s.InputDirection),
		InputThrust:    s.InputThrust,
		MaxHp:          s.MaxHP,
		Hp:             s.HP,
		MaxEnergy:      s.MaxEnergy,
		Energy:         s.Energy,
	}
}

// DetectCollision returns true if receiver spaceship collides with other spaceship.
func (s *Spaceship) DetectCollision(other *Spaceship) bool {
	v := types.Point{X: s.Position.X - other.Position.X, Y: s.Position.Y - other.Position.Y}
	dist := v.Length()

	return dist < 2*constants.SpaceshipSize
}

// Collide transforms colliding ships' parameters.
func (s *Spaceship) Collide(other *Spaceship) {
	v := types.Point{
		X: s.Position.X - other.Position.X,
		Y: s.Position.Y - other.Position.Y,
	}

	transformAngle := -math.Atan2(float64(v.Y), float64(v.X))
	newV1 := s.Velocity.Rotate(transformAngle)
	newV2 := other.Velocity.Rotate(transformAngle)

	switchedV1 := types.Vector{X: newV2.X, Y: newV1.Y}
	switchedV2 := types.Vector{X: newV1.X, Y: newV2.Y}

	s.Velocity = switchedV1.Rotate(-transformAngle)
	other.Velocity = switchedV2.Rotate(-transformAngle)

	s.Dirty = true
	other.Dirty = true
}

func (s *Spaceship) ShootIfPossible() (canShoot bool) {
	if s.Energy >= constants.BasicWeaponEnergyCost {
		s.Energy -= constants.BasicWeaponEnergyCost
		canShoot = true
	} else {
		canShoot = false
	}
	return canShoot
}

func (s *Spaceship) CollideWithProjectile(projectile *Projectile) {
	if s.HP < constants.ProjectileDamage {
		s.HP = 0
	} else {
		s.HP -= constants.ProjectileDamage
	}
	s.AutoRepairDelay = constants.AutoRepairDelay

	s.Dirty = true
}

func (s *Spaceship) AddReward(reward uint32) {
	s.HP += reward
	s.MaxHP += reward
}

func (s *Spaceship) AddEnergyReward(reward uint32) {
	s.Energy += reward
	s.MaxEnergy += reward
}

func (s *Spaceship) AutoRepair() {
	s.HP += constants.AutoRepairAmount

	if s.HP > s.MaxHP {
		s.HP = s.MaxHP
	}
	s.AutoRepairDelay = constants.AutoRepairInterval
}

func (s *Spaceship) AutoEnergyRecharge() {
	s.Energy += constants.AutoEnergyRechargeAmount

	if s.Energy > s.MaxEnergy {
		s.Energy = s.MaxEnergy
	}
	s.AutoEnergyRechargeDelay = constants.AutoEnergyRechargeInterval
}

func (s *Spaceship) LeftTurn() {
	s.AngularVelocityDelta = s.angularVelocityDelta()
	s.LimitAngularVelocityDelta()
}

func (s *Spaceship) RightTurn() {
	s.AngularVelocityDelta = -s.angularVelocityDelta()
	s.LimitAngularVelocityDelta()
}

func (s *Spaceship) TurnToTarget() {
	targetAngle := *s.TargetAngle
	offset := targetAngle - s.Facing

	if math.Abs(offset) > math.Pi {
		offset -= math.Copysign(2*math.Pi, offset)
	}

	targetAngularVelocity := -offset * constants.SpaceshipTurnToAngleP
	s.AngularVelocityDelta = targetAngularVelocity - s.AngularVelocity

	s.LimitAngularVelocityDelta()
}

func (s *Spaceship) LimitAngularVelocityDelta() {
	potentialAngularVelocity := s.AngularVelocity + s.AngularVelocityDelta;
	diff := math.Abs(potentialAngularVelocity) - constants.SpaceshipMaxAngularVelocity

	if (diff > 0) {
		s.AngularVelocityDelta -= math.Copysign(diff, s.AngularVelocity)
	}
}

func (s *Spaceship) ApplyAngularFriction() {
	s.AngularVelocity *= (1 - constants.SpaceshipAngularFriction)
}

func (s *Spaceship) angularVelocityDelta() float64 {
	nonlinearPart := constants.SpaceshipNonlinearAngularAcceleration * math.Abs(s.AngularVelocity)
	linearPart := constants.SpaceshipLinearAngularAcceleration
	return nonlinearPart + linearPart
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/thoas/stats"
	"github.com/unrolled/render"
)

func TestListUsersHandler(t *testing.T) {
	env := Env{
		Metrics: stats.New(),
		Render:  render.New(),
	}
	req, _ := http.NewRequest("GET", "http://localhost:3009/users", nil)
	w := httptest.NewRecorder()
	makeHandler(env, ListUsersHandler).ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("Home page didn't return %v", http.StatusOK)
	}
}
<commit_msg>#59 experiment with setup/teardown<commit_after>package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/thoas/stats"
	"github.com/unrolled/render"
)

func HandlersTestSetup() Env {
	env := Env{
		Metrics: stats.New(),
		Render:  render.New(),
	}
	return env
}

func teardown() {

}

func TestListUsersHandler(t *testing.T) {
	env := HandlersTestSetup()
	req, _ := http.NewRequest("GET", "", nil)
	w := httptest.NewRecorder()
	makeHandler(env, ListUsersHandler).ServeHTTP(w, req)
	fmt.Println(w)
	if w.Code != http.StatusOK {
		t.Errorf("Home page didn't return %v", http.StatusOK)
	}
	fmt.Println(w.Code)
	fmt.Println(w.HeaderMap)
	fmt.Println(w.Body)
}
<|endoftext|>


<commit_before>package gocsv

import (
	"reflect"
	"testing"
)

type sampleTypeUnmarshaller struct {
	val string
}

func (s *sampleTypeUnmarshaller) UnmarshalCSV(val string) error {
	s.val = val
	return nil
}

func (s sampleTypeUnmarshaller) MarshalCSV() (string, error) {
	return s.val, nil
}

type sampleTextUnmarshaller struct {
	val []byte
}

func (s *sampleTextUnmarshaller) UnmarshalText(text []byte) error {
	s.val = text
	return nil
}

func (s sampleTextUnmarshaller) MarshalText() ([]byte, error) {
	return s.val, nil
}

type sampleStringer string

func (s sampleStringer) String() string {
	return string(s)
}

type customStringAlias string

func (s customStringAlias) MarshalCSV() (string, error) {
	return `"` + string(s) + `"`, nil
}

func Test_getFieldAsString_CustomStringAlias(t *testing.T) {
	s, err := getFieldAsString(reflect.ValueOf(customStringAlias(("foo"))))
	if err != nil {
		t.Fatalf("getFieldAsString failure: %s", err)
	}

	if string(s) != `"foo"` {
		t.Fatalf(`expected "foo" got %s`, s)
	}
}

func Benchmark_unmarshall_TypeUnmarshaller(b *testing.B) {
	sample := sampleTypeUnmarshaller{}
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		if err := unmarshall(val, "foo"); err != nil {
			b.Fatalf("unmarshall error: %s", err.Error())
		}
	}
}

func Benchmark_unmarshall_TextUnmarshaller(b *testing.B) {
	sample := sampleTextUnmarshaller{}
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		if err := unmarshall(val, "foo"); err != nil {
			b.Fatalf("unmarshall error: %s", err.Error())
		}
	}
}

func Benchmark_marshall_TypeMarshaller(b *testing.B) {
	sample := sampleTypeUnmarshaller{"foo"}
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		_, err := marshall(val)
		if err != nil {
			b.Fatalf("marshall error: %s", err.Error())
		}
	}
}

func Benchmark_marshall_TextMarshaller(b *testing.B) {
	sample := sampleTextUnmarshaller{[]byte("foo")}
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		_, err := marshall(val)
		if err != nil {
			b.Fatalf("marshall error: %s", err.Error())
		}
	}
}

func Benchmark_marshall_Stringer(b *testing.B) {
	sample := sampleStringer("foo")
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		_, err := marshall(val)
		if err != nil {
			b.Fatalf("marshall error: %s", err.Error())
		}
	}
}

func TestToInt(t *testing.T) {
	TestCase := []struct {
		field  string
		result int
		err    error
	}{
		{"123.2", 123, nil},
		{"123", 123, nil},
		{"1.2.3", 1, nil},
		{"0.123", 0, nil},
	}

	for idx, item := range TestCase {
		out, err := toInt(item.field)
		if err != nil {
			t.Fatal(err)
		}
		if int(out) != item.result {
			t.Fatal(idx, "result not equal")
		}
	}
}
<commit_msg>cleanup<commit_after>package gocsv

import (
	"reflect"
	"testing"
)

type sampleTypeUnmarshaller struct {
	val string
}

func (s *sampleTypeUnmarshaller) UnmarshalCSV(val string) error {
	s.val = val
	return nil
}

func (s sampleTypeUnmarshaller) MarshalCSV() (string, error) {
	return s.val, nil
}

type sampleTextUnmarshaller struct {
	val []byte
}

func (s *sampleTextUnmarshaller) UnmarshalText(text []byte) error {
	s.val = text
	return nil
}

func (s sampleTextUnmarshaller) MarshalText() ([]byte, error) {
	return s.val, nil
}

type sampleStringer string

func (s sampleStringer) String() string {
	return string(s)
}

type stringAlias string
type customStringAlias string

func (s customStringAlias) MarshalCSV() (string, error) {
	return `"` + string(s) + `"`, nil
}

func Test_getFieldAsString_CustomStringAlias(t *testing.T) {
	s, err := getFieldAsString(reflect.ValueOf(customStringAlias("foo")))
	if err != nil {
		t.Fatalf("getFieldAsString failure: %s", err)
	}

	if string(s) != `"foo"` {
		t.Fatalf(`expected "foo" got %s`, s)
	}

	s, err = getFieldAsString(reflect.ValueOf(stringAlias("foo")))
	if err != nil {
		t.Fatalf("getFieldAsString failure: %s", err)
	}

	if string(s) != `foo` {
		t.Fatalf(`expected foo got %s`, s)
	}
}

func Benchmark_unmarshall_TypeUnmarshaller(b *testing.B) {
	sample := sampleTypeUnmarshaller{}
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		if err := unmarshall(val, "foo"); err != nil {
			b.Fatalf("unmarshall error: %s", err.Error())
		}
	}
}

func Benchmark_unmarshall_TextUnmarshaller(b *testing.B) {
	sample := sampleTextUnmarshaller{}
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		if err := unmarshall(val, "foo"); err != nil {
			b.Fatalf("unmarshall error: %s", err.Error())
		}
	}
}

func Benchmark_marshall_TypeMarshaller(b *testing.B) {
	sample := sampleTypeUnmarshaller{"foo"}
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		_, err := marshall(val)
		if err != nil {
			b.Fatalf("marshall error: %s", err.Error())
		}
	}
}

func Benchmark_marshall_TextMarshaller(b *testing.B) {
	sample := sampleTextUnmarshaller{[]byte("foo")}
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		_, err := marshall(val)
		if err != nil {
			b.Fatalf("marshall error: %s", err.Error())
		}
	}
}

func Benchmark_marshall_Stringer(b *testing.B) {
	sample := sampleStringer("foo")
	val := reflect.ValueOf(&sample)
	for n := 0; n < b.N; n++ {
		_, err := marshall(val)
		if err != nil {
			b.Fatalf("marshall error: %s", err.Error())
		}
	}
}

func TestToInt(t *testing.T) {
	TestCase := []struct {
		field  string
		result int
		err    error
	}{
		{"123.2", 123, nil},
		{"123", 123, nil},
		{"1.2.3", 1, nil},
		{"0.123", 0, nil},
	}

	for idx, item := range TestCase {
		out, err := toInt(item.field)
		if err != nil {
			t.Fatal(err)
		}
		if int(out) != item.result {
			t.Fatal(idx, "result not equal")
		}
	}
}
<|endoftext|>


<commit_before>// Compiles a fiddle and then runs the fiddle. The output of both processes is
// combined into a single JSON output.
package main

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/mux"
	"go.skia.org/infra/fiddlek/go/types"
	"go.skia.org/infra/go/common"
	"go.skia.org/infra/go/exec"
	"go.skia.org/infra/go/httputils"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
	"go.skia.org/infra/go/util/limitwriter"
	"golang.org/x/sync/errgroup"
)

const (
	// FPS is the Frames Per Second when generating an animation.
	FPS = 60
)

// flags
var (
	apoptosis  = flag.Duration("apoptosis", 5*time.Minute, "How long a pod should live after starting a run.")
	local      = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
	fiddleRoot = flag.String("fiddle_root", "", "Directory location where all the work is done.")
	checkout   = flag.String("checkout", "", "Directory where Skia is checked out.")
	port       = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
)

var (
	mutex        sync.Mutex
	currentState types.State = types.IDLE
	version      string
)

func setStateStart() error {
	mutex.Lock()
	defer mutex.Unlock()
	if currentState != types.IDLE {
		return fmt.Errorf("Fiddle already being run.")
	}
	currentState = types.WRITING
	return nil
}

func setState(s types.State) {
	mutex.Lock()
	defer mutex.Unlock()
	currentState = s
}

func getState() types.State {
	mutex.Lock()
	defer mutex.Unlock()
	return currentState
}

func serializeOutput(w io.Writer, res *types.Result) {
	w = limitwriter.New(w, types.MAX_JSON_SIZE)
	if err := json.NewEncoder(w).Encode(res); err != nil {
		sklog.Errorf("Failed to encode: %s", err)
	}
}

func mainHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	resp := &types.FiddlerMainResponse{
		State:   getState(),
		Version: version,
	}

	if err := json.NewEncoder(w).Encode(resp); err != nil {
		sklog.Warningf("Failed to write response: %s", err)
	}
}

func runHandler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	defer util.Close(r.Body)
	if setStateStart() != nil {
		http.Error(w, "Currently running a fiddle.", http.StatusTooManyRequests)
		return
	}
	go func() {
		// Fail out of the container after a single run.
		<-time.Tick(*apoptosis)
		os.Exit(0)
	}()
	var request types.FiddleContext

	res := &types.Result{
		Compile: types.Compile{},
		Execute: types.Execute{
			Errors: "",
			Output: types.Output{},
		},
	}

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		res.Execute.Errors = fmt.Sprintf("Invalid JSON Request: %s", err)
		serializeOutput(w, res)
		return
	}

	// Compile draw.cpp into 'fiddle'.
	if err := ioutil.WriteFile(filepath.Join(*checkout, "tools", "fiddle", "draw.cpp"), []byte(request.Code), 0644); err != nil {
		res.Execute.Errors = fmt.Sprintf("Failed to write draw.cpp: %s", err)
		serializeOutput(w, res)
		return
	}

	setState(types.COMPILING)

	// TODO(jcgregorio) Put a timeout here.
	buildResults, err := exec.RunCwd(ctx, *checkout, filepath.Join(*fiddleRoot, "depot_tools", "ninja"), "-C", "out/Static")
	if err != nil {
		res.Compile.Errors = err.Error()
		res.Compile.Output = buildResults
		serializeOutput(w, res)
		return
	}

	// We stay in the RUNNING state forever.
	setState(types.RUNNING)

	if request.Options.Duration == 0 {
		oneStep(ctx, *checkout, res, 0.0, request.Options.Duration)
		serializeOutput(w, res)
	} else {

		var g errgroup.Group
		// If this is an animated fiddle then:
		//   - Create tmpdir to store PNGs.
		//   - Loop over the following code to generate each frame of the animation.
		//   -   Pass the duration and frame via cmd line flags to fiddle.
		//   -   Decode and write PNGs (CPU+GPU) to their temp location.
		//   - Run ffmpeg over the resulting PNG's to generate the webm files.
		//   - Clean up tmp file.
		//   - Encode resulting webm files as base64 strings and return in JSON.
		numFrames := int(FPS * (request.Options.Duration))
		tmpDir, err := ioutil.TempDir("", "animation")
		defer util.RemoveAll(tmpDir)
		if err != nil {
			res.Execute.Errors = fmt.Sprintf("Failed to create tmp dir for storing animation PNGs: %s", err)
			serializeOutput(w, res)
			return
		}

		// frameCh is a channel of all the frames we want rendered, which feeds the
		// pool of Go routines that will do the actual rendering.
		frameCh := make(chan int)

		// mutex protects glinfo.
		var mutex sync.Mutex
		// glinfo is the info about the GL version for the GPU.
		glinfo := ""

		// Start a pool of workers to do the rendering.
		for i := 0; i <= 5; i++ {
			g.Go(func() error {
				// Each Go func should have its own 'res'.
				res := &types.Result{
					Compile: types.Compile{},
					Execute: types.Execute{
						Errors: "",
						Output: types.Output{},
					},
				}
				for frameIndex := range frameCh {
					sklog.Infof("Parallel render: %d", frameIndex)
					frame := float64(frameIndex) / float64(numFrames)
					oneStep(ctx, *checkout, res, frame, request.Options.Duration)
					// Check for errors.
					if res.Execute.Errors != "" {
						return fmt.Errorf("Failed to render: %s", res.Execute.Errors)
					}
					// Extract CPU and GPU pngs to a tmp directory.
					if err := extractPNG(res.Execute.Output.Raster, res, frameIndex, "CPU", tmpDir); err != nil {
						return err
					}
					if err := extractPNG(res.Execute.Output.Gpu, res, frameIndex, "GPU", tmpDir); err != nil {
						return err
					}
					mutex.Lock()
					if glinfo == "" {
						glinfo = res.Execute.Output.GLInfo
					}
					mutex.Unlock()
				}
				return nil
			})
		}

		// Feed all the frame indices to the channel.
		for i := 0; i <= numFrames; i++ {
			frameCh <- i
		}
		close(frameCh)

		// Wait for all the work to be done.
		if err := g.Wait(); err != nil {
			res.Execute.Errors = fmt.Sprintf("Failed to encode video: %s", err)
			serializeOutput(w, res)
			return
		}
		// Run ffmpeg for CPU and GPU.
		if err := createWebm(ctx, "CPU", tmpDir); err != nil {
			res.Execute.Errors = fmt.Sprintf("Failed to encode video: %s", err)
			serializeOutput(w, res)
			return
		}
		res.Execute.Output.AnimatedRaster = encodeWebm("CPU", tmpDir, res)

		if err := createWebm(ctx, "GPU", tmpDir); err != nil {
			res.Execute.Errors = fmt.Sprintf("Failed to encode video: %s", err)
			serializeOutput(w, res)
			return
		}
		res.Execute.Output.AnimatedGpu = encodeWebm("GPU", tmpDir, res)
		res.Execute.Output.Raster = ""
		res.Execute.Output.Gpu = ""
		res.Execute.Output.GLInfo = glinfo

		serializeOutput(w, res)
	}

	go func() {
		// Fail out of the container after the results have been sent.
		<-time.Tick(3 * time.Second)
		os.Exit(0)
	}()
}

// encodeWebm encodes the webm as base64 and adds it to the results.
func encodeWebm(prefix, tmpDir string, res *types.Result) string {
	b, err := ioutil.ReadFile(path.Join(tmpDir, fmt.Sprintf("%s.webm", prefix)))
	if err != nil {
		res.Execute.Errors = fmt.Sprintf("Failed to read resulting video: %s", err)
		return ""
	}
	return base64.StdEncoding.EncodeToString(b)
}

// createWebm runs ffmpeg over the images in the given dir.
func createWebm(ctx context.Context, prefix, tmpDir string) error {
	// ffmpeg -r $FPS -pattern_type glob -i '*.png' -c:v libvpx-vp9 -lossless 1 output.webm
	name := "ffmpeg"
	args := []string{
		"-r", fmt.Sprintf("%d", FPS),
		"-pattern_type", "glob", "-i", prefix + "*.png",
		"-c:v", "libvpx-vp9",
		"-lossless", "1",
		fmt.Sprintf("%s.webm", prefix),
	}
	output := &bytes.Buffer{}
	runCmd := &exec.Command{
		Name:      name,
		Args:      args,
		Dir:       tmpDir,
		LogStderr: true,
		Stdout:    output,
	}
	if err := exec.Run(ctx, runCmd); err != nil {
		return fmt.Errorf("ffmpeg failed %#v: %s", *runCmd, err)
	}

	return nil
}

// extractPNG pulls the base64 encoded PNG out of the results and writes it to the tmpDir.
func extractPNG(b64 string, res *types.Result, i int, prefix string, tmpDir string) error {
	body, err := base64.StdEncoding.DecodeString(b64)
	if err != nil {
		res.Execute.Errors = fmt.Sprintf("Failed to decode frame %d of %s: %s", i, prefix, err)
		return err
	}
	if err := ioutil.WriteFile(path.Join(tmpDir, fmt.Sprintf("%s_%05d.png", prefix, i)), body, 0600); err != nil {
		res.Execute.Errors = fmt.Sprintf("Failed to write frame %d of %s as a PNG: %s", i, prefix, err)
		return err
	}
	return nil
}

func oneStep(ctx context.Context, checkout string, res *types.Result, frame float64, duration float64) {
	name := path.Join(checkout, "out", "Static", "fiddle")
	args := []string{"--duration", fmt.Sprintf("%f", duration), "--frame", fmt.Sprintf("%f", frame)}
	stderr := bytes.Buffer{}
	stdout := bytes.Buffer{}
	runCmd := &exec.Command{
		Name:        name,
		Args:        args,
		Dir:         *fiddleRoot,
		InheritPath: true,
		Env:         []string{"HOME=/tmp"},
		InheritEnv:  true,
		Stdout:      &stdout,
		Stderr:      &stderr,
		Timeout:     20 * time.Second,
	}
	if err := exec.Run(ctx, runCmd); err != nil {
		sklog.Errorf("Failed to run: %s", err)
		res.Execute.Errors = err.Error()
	}
	if res.Execute.Errors != "" && stderr.String() != "" {
		sklog.Errorf("Found stderr output: %q", stderr.String())
		res.Execute.Errors += "\n"
	}
	res.Execute.Errors += stderr.String()
	if err := json.Unmarshal(stdout.Bytes(), &res.Execute.Output); err != nil {
		if res.Execute.Errors != "" {
			res.Execute.Errors += "\n"
		}
		res.Execute.Errors += "Failed to decode JSON output from fiddle.\n"
		res.Execute.Errors += err.Error()
		res.Execute.Errors += fmt.Sprintf("\nOutput was %q", stdout.Bytes())
	}
}

func main() {
	common.InitWithMust(
		"fiddler",
	)
	if *fiddleRoot == "" {
		sklog.Fatalf("The --fiddle_root flag is required.")
	}
	if *checkout == "" {
		sklog.Fatalf("The --checkout flag is required.")
	}

	b, err := ioutil.ReadFile(filepath.Join(*checkout, "VERSION"))
	if err != nil {
		sklog.Fatalf("Failed to read Skia version: %s", err)
	}
	version = strings.TrimSpace(string(b))

	r := mux.NewRouter()
	r.HandleFunc("/", mainHandler)
	r.HandleFunc("/run", runHandler)

	sklog.Infoln("Ready to serve.")
	srv := &http.Server{
		Handler:      httputils.LoggingGzipRequestResponse(r),
		Addr:         *port,
		WriteTimeout: 120 * time.Second,
		ReadTimeout:  120 * time.Second,
	}

	sklog.Fatal(srv.ListenAndServe())
}
<commit_msg>fiddler - Don't log ffmpeg output to error logs.<commit_after>// Compiles a fiddle and then runs the fiddle. The output of both processes is
// combined into a single JSON output.
package main

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/mux"
	"go.skia.org/infra/fiddlek/go/types"
	"go.skia.org/infra/go/common"
	"go.skia.org/infra/go/exec"
	"go.skia.org/infra/go/httputils"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
	"go.skia.org/infra/go/util/limitwriter"
	"golang.org/x/sync/errgroup"
)

const (
	// FPS is the Frames Per Second when generating an animation.
	FPS = 60
)

// flags
var (
	apoptosis  = flag.Duration("apoptosis", 5*time.Minute, "How long a pod should live after starting a run.")
	local      = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
	fiddleRoot = flag.String("fiddle_root", "", "Directory location where all the work is done.")
	checkout   = flag.String("checkout", "", "Directory where Skia is checked out.")
	port       = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
)

var (
	mutex        sync.Mutex
	currentState types.State = types.IDLE
	version      string
)

func setStateStart() error {
	mutex.Lock()
	defer mutex.Unlock()
	if currentState != types.IDLE {
		return fmt.Errorf("Fiddle already being run.")
	}
	currentState = types.WRITING
	return nil
}

func setState(s types.State) {
	mutex.Lock()
	defer mutex.Unlock()
	currentState = s
}

func getState() types.State {
	mutex.Lock()
	defer mutex.Unlock()
	return currentState
}

func serializeOutput(w io.Writer, res *types.Result) {
	w = limitwriter.New(w, types.MAX_JSON_SIZE)
	if err := json.NewEncoder(w).Encode(res); err != nil {
		sklog.Errorf("Failed to encode: %s", err)
	}
}

func mainHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	resp := &types.FiddlerMainResponse{
		State:   getState(),
		Version: version,
	}

	if err := json.NewEncoder(w).Encode(resp); err != nil {
		sklog.Warningf("Failed to write response: %s", err)
	}
}

func runHandler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	defer util.Close(r.Body)
	if setStateStart() != nil {
		http.Error(w, "Currently running a fiddle.", http.StatusTooManyRequests)
		return
	}
	go func() {
		// Fail out of the container after a single run.
		<-time.Tick(*apoptosis)
		os.Exit(0)
	}()
	var request types.FiddleContext

	res := &types.Result{
		Compile: types.Compile{},
		Execute: types.Execute{
			Errors: "",
			Output: types.Output{},
		},
	}

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		res.Execute.Errors = fmt.Sprintf("Invalid JSON Request: %s", err)
		serializeOutput(w, res)
		return
	}

	// Compile draw.cpp into 'fiddle'.
	if err := ioutil.WriteFile(filepath.Join(*checkout, "tools", "fiddle", "draw.cpp"), []byte(request.Code), 0644); err != nil {
		res.Execute.Errors = fmt.Sprintf("Failed to write draw.cpp: %s", err)
		serializeOutput(w, res)
		return
	}

	setState(types.COMPILING)

	// TODO(jcgregorio) Put a timeout here.
	buildResults, err := exec.RunCwd(ctx, *checkout, filepath.Join(*fiddleRoot, "depot_tools", "ninja"), "-C", "out/Static")
	if err != nil {
		res.Compile.Errors = err.Error()
		res.Compile.Output = buildResults
		serializeOutput(w, res)
		return
	}

	// We stay in the RUNNING state forever.
	setState(types.RUNNING)

	if request.Options.Duration == 0 {
		oneStep(ctx, *checkout, res, 0.0, request.Options.Duration)
		serializeOutput(w, res)
	} else {

		var g errgroup.Group
		// If this is an animated fiddle then:
		//   - Create tmpdir to store PNGs.
		//   - Loop over the following code to generate each frame of the animation.
		//   -   Pass the duration and frame via cmd line flags to fiddle.
		//   -   Decode and write PNGs (CPU+GPU) to their temp location.
		//   - Run ffmpeg over the resulting PNG's to generate the webm files.
		//   - Clean up tmp file.
		//   - Encode resulting webm files as base64 strings and return in JSON.
		numFrames := int(FPS * (request.Options.Duration))
		tmpDir, err := ioutil.TempDir("", "animation")
		defer util.RemoveAll(tmpDir)
		if err != nil {
			res.Execute.Errors = fmt.Sprintf("Failed to create tmp dir for storing animation PNGs: %s", err)
			serializeOutput(w, res)
			return
		}

		// frameCh is a channel of all the frames we want rendered, which feeds the
		// pool of Go routines that will do the actual rendering.
		frameCh := make(chan int)

		// mutex protects glinfo.
		var mutex sync.Mutex
		// glinfo is the info about the GL version for the GPU.
		glinfo := ""

		// Start a pool of workers to do the rendering.
		for i := 0; i <= 5; i++ {
			g.Go(func() error {
				// Each Go func should have its own 'res'.
				res := &types.Result{
					Compile: types.Compile{},
					Execute: types.Execute{
						Errors: "",
						Output: types.Output{},
					},
				}
				for frameIndex := range frameCh {
					sklog.Infof("Parallel render: %d", frameIndex)
					frame := float64(frameIndex) / float64(numFrames)
					oneStep(ctx, *checkout, res, frame, request.Options.Duration)
					// Check for errors.
					if res.Execute.Errors != "" {
						return fmt.Errorf("Failed to render: %s", res.Execute.Errors)
					}
					// Extract CPU and GPU pngs to a tmp directory.
					if err := extractPNG(res.Execute.Output.Raster, res, frameIndex, "CPU", tmpDir); err != nil {
						return err
					}
					if err := extractPNG(res.Execute.Output.Gpu, res, frameIndex, "GPU", tmpDir); err != nil {
						return err
					}
					mutex.Lock()
					if glinfo == "" {
						glinfo = res.Execute.Output.GLInfo
					}
					mutex.Unlock()
				}
				return nil
			})
		}

		// Feed all the frame indices to the channel.
		for i := 0; i <= numFrames; i++ {
			frameCh <- i
		}
		close(frameCh)

		// Wait for all the work to be done.
		if err := g.Wait(); err != nil {
			res.Execute.Errors = fmt.Sprintf("Failed to encode video: %s", err)
			serializeOutput(w, res)
			return
		}
		// Run ffmpeg for CPU and GPU.
		if err := createWebm(ctx, "CPU", tmpDir); err != nil {
			res.Execute.Errors = fmt.Sprintf("Failed to encode video: %s", err)
			serializeOutput(w, res)
			return
		}
		res.Execute.Output.AnimatedRaster = encodeWebm("CPU", tmpDir, res)

		if err := createWebm(ctx, "GPU", tmpDir); err != nil {
			res.Execute.Errors = fmt.Sprintf("Failed to encode video: %s", err)
			serializeOutput(w, res)
			return
		}
		res.Execute.Output.AnimatedGpu = encodeWebm("GPU", tmpDir, res)
		res.Execute.Output.Raster = ""
		res.Execute.Output.Gpu = ""
		res.Execute.Output.GLInfo = glinfo

		serializeOutput(w, res)
	}

	go func() {
		// Fail out of the container after the results have been sent.
		<-time.Tick(3 * time.Second)
		os.Exit(0)
	}()
}

// encodeWebm encodes the webm as base64 and adds it to the results.
func encodeWebm(prefix, tmpDir string, res *types.Result) string {
	b, err := ioutil.ReadFile(path.Join(tmpDir, fmt.Sprintf("%s.webm", prefix)))
	if err != nil {
		res.Execute.Errors = fmt.Sprintf("Failed to read resulting video: %s", err)
		return ""
	}
	return base64.StdEncoding.EncodeToString(b)
}

func trunc(s string) string {
	if len(s) < 100 {
		return s
	}
	return s[:100] + "..."
}

// createWebm runs ffmpeg over the images in the given dir.
func createWebm(ctx context.Context, prefix, tmpDir string) error {
	// ffmpeg -r $FPS -pattern_type glob -i '*.png' -c:v libvpx-vp9 -lossless 1 output.webm
	name := "ffmpeg"
	args := []string{
		"-r", fmt.Sprintf("%d", FPS),
		"-pattern_type", "glob", "-i", prefix + "*.png",
		"-c:v", "libvpx-vp9",
		"-lossless", "1",
		fmt.Sprintf("%s.webm", prefix),
	}
	output := &bytes.Buffer{}
	runCmd := &exec.Command{
		Name:           name,
		Args:           args,
		Dir:            tmpDir,
		CombinedOutput: output,
	}
	if err := exec.Run(ctx, runCmd); err != nil {
		return fmt.Errorf("ffmpeg failed %#v %q: %s", *runCmd, trunc(output.String()), err)
	}

	return nil
}

// extractPNG pulls the base64 encoded PNG out of the results and writes it to the tmpDir.
func extractPNG(b64 string, res *types.Result, i int, prefix string, tmpDir string) error {
	body, err := base64.StdEncoding.DecodeString(b64)
	if err != nil {
		res.Execute.Errors = fmt.Sprintf("Failed to decode frame %d of %s: %s", i, prefix, err)
		return err
	}
	if err := ioutil.WriteFile(path.Join(tmpDir, fmt.Sprintf("%s_%05d.png", prefix, i)), body, 0600); err != nil {
		res.Execute.Errors = fmt.Sprintf("Failed to write frame %d of %s as a PNG: %s", i, prefix, err)
		return err
	}
	return nil
}

func oneStep(ctx context.Context, checkout string, res *types.Result, frame float64, duration float64) {
	name := path.Join(checkout, "out", "Static", "fiddle")
	args := []string{"--duration", fmt.Sprintf("%f", duration), "--frame", fmt.Sprintf("%f", frame)}
	stderr := bytes.Buffer{}
	stdout := bytes.Buffer{}
	runCmd := &exec.Command{
		Name:        name,
		Args:        args,
		Dir:         *fiddleRoot,
		InheritPath: true,
		Env:         []string{"HOME=/tmp"},
		InheritEnv:  true,
		Stdout:      &stdout,
		Stderr:      &stderr,
		Timeout:     20 * time.Second,
	}
	if err := exec.Run(ctx, runCmd); err != nil {
		sklog.Errorf("Failed to run: %s", err)
		res.Execute.Errors = err.Error()
	}
	if res.Execute.Errors != "" && stderr.String() != "" {
		sklog.Errorf("Found stderr output: %q", stderr.String())
		res.Execute.Errors += "\n"
	}
	res.Execute.Errors += stderr.String()
	if err := json.Unmarshal(stdout.Bytes(), &res.Execute.Output); err != nil {
		if res.Execute.Errors != "" {
			res.Execute.Errors += "\n"
		}
		res.Execute.Errors += "Failed to decode JSON output from fiddle.\n"
		res.Execute.Errors += err.Error()
		res.Execute.Errors += fmt.Sprintf("\nOutput was %q", stdout.Bytes())
	}
}

func main() {
	common.InitWithMust(
		"fiddler",
	)
	if *fiddleRoot == "" {
		sklog.Fatalf("The --fiddle_root flag is required.")
	}
	if *checkout == "" {
		sklog.Fatalf("The --checkout flag is required.")
	}

	b, err := ioutil.ReadFile(filepath.Join(*checkout, "VERSION"))
	if err != nil {
		sklog.Fatalf("Failed to read Skia version: %s", err)
	}
	version = strings.TrimSpace(string(b))

	r := mux.NewRouter()
	r.HandleFunc("/", mainHandler)
	r.HandleFunc("/run", runHandler)

	sklog.Infoln("Ready to serve.")
	srv := &http.Server{
		Handler:      httputils.LoggingGzipRequestResponse(r),
		Addr:         *port,
		WriteTimeout: 120 * time.Second,
		ReadTimeout:  120 * time.Second,
	}

	sklog.Fatal(srv.ListenAndServe())
}
<|endoftext|>


<commit_before>// DO NOT EDIT. Generated by 'gorums' plugin for protoc-gen-go
// Source file to edit is: config_qc_tmpl

package dev

import (
	"fmt"
	"sync"

	"golang.org/x/net/context"
)

// ReadReply encapsulates the reply from a Read quorum call.
// It contains the id of each node of the quorum that replied and a single reply.
type ReadReply struct {
	NodeIDs []uint32
	*State
}

func (r ReadReply) String() string {
	return fmt.Sprintf("node ids: %v | answer: %v", r.NodeIDs, r.State)
}

// Read invokes a Read quorum call on configuration c
// and returns the result as a ReadReply.
func (c *Configuration) Read(ctx context.Context, args *ReadRequest) (*ReadReply, error) {
	return c.mgr.read(ctx, c, args)
}

// ReadFuture is a reference to an asynchronous Read quorum call invocation.
type ReadFuture struct {
	reply *ReadReply
	err   error
	c     chan struct{}
}

// ReadFuture asynchronously invokes a Read quorum call
// on configuration c and returns a ReadFuture which can be used to
// inspect the quorum call reply and error when available.
func (c *Configuration) ReadFuture(ctx context.Context, args *ReadRequest) *ReadFuture {
	f := new(ReadFuture)
	f.c = make(chan struct{}, 1)
	go func() {
		defer close(f.c)
		f.reply, f.err = c.mgr.read(ctx, c, args)
	}()
	return f
}

// Get returns the reply and any error associated with the ReadFuture.
// The method blocks until a reply or error is available.
func (f *ReadFuture) Get() (*ReadReply, error) {
	<-f.c
	return f.reply, f.err
}

// Done reports if a reply and/or error is available for the ReadFuture.
func (f *ReadFuture) Done() bool {
	select {
	case <-f.c:
		return true
	default:
		return false
	}
}

// ReadCorrectable asynchronously invokes a
// correctable Read quorum call on configuration c and returns a
// ReadCorrectable which can be used to inspect any repies or errors
// when available.
func (c *Configuration) ReadCorrectable(ctx context.Context, args *ReadRequest) *ReadCorrectable {
	corr := &ReadCorrectable{
		level:  LevelNotSet,
		donech: make(chan struct{}),
	}
	go func() {
		c.mgr.readCorrectable(ctx, c, corr, args)
	}()
	return corr
}

// ReadCorrectable is a reference to a correctable Read quorum call.
type ReadCorrectable struct {
	mu       sync.Mutex
	reply    *ReadReply
	level    int
	err      error
	done     bool
	watchers []*struct {
		level int
		ch    chan struct{}
	}
	donech chan struct{}
}

// Get returns the reply, level and any error associated with the
// ReadCorrectable. The method does not block until a (possibly
// itermidiate) reply or error is available. Level is set to LevelNotSet if no
// reply has yet been received. The Done or Watch methods should be used to
// ensure that a reply is available.
func (c *ReadCorrectable) Get() (*ReadReply, int, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.reply, c.level, c.err
}

// Done returns a channel that's closed when the Read correctable
// quorum call is done. A call is considered done when the quorum function has
// signaled that a quorum of replies was received or that the call returned an
// error.
func (c *ReadCorrectable) Done() <-chan struct{} {
	return c.donech
}

// Watch returns a channel that's closed when a reply or error at or above the
// specified level is available. If the call is done, the channel is closed
// disregardless of the specified level.
func (c *ReadCorrectable) Watch(level int) <-chan struct{} {
	ch := make(chan struct{})
	c.mu.Lock()
	if level < c.level {
		close(ch)
		c.mu.Unlock()
		return ch
	}
	c.watchers = append(c.watchers, &struct {
		level int
		ch    chan struct{}
	}{level, ch})
	c.mu.Unlock()
	return ch
}

func (c *ReadCorrectable) set(reply *ReadReply, level int, err error, done bool) {
	c.mu.Lock()
	if c.done {
		c.mu.Unlock()
		panic("set(...) called on a done correctable")
	}
	c.reply, c.level, c.err, c.done = reply, level, err, done
	if done {
		close(c.donech)
		for _, watcher := range c.watchers {
			if watcher != nil {
				close(watcher.ch)
			}
		}
		c.mu.Unlock()
		return
	}
	for i := range c.watchers {
		if c.watchers[i].level <= level {
			close(c.watchers[i].ch)
			c.watchers[i] = nil
		}
	}
	c.mu.Unlock()
}

// ReadTwoReply encapsulates the reply from a Read quorum call.
// It contains the id of each node of the quorum that replied and a single reply.
type ReadTwoReply struct {
	NodeIDs []uint32
	*State
}

func (r ReadTwoReply) String() string {
	return fmt.Sprintf("node ids: %v | answer: %v", r.NodeIDs, r.State)
}

// ReadTwoCorrectablePR asynchronously invokes a correctable Read quorum call
// with server side preliminary reply support on configuration c and returns a
// ReadCorrectable which can be used to inspect any repies or errors
// when available.
func (c *Configuration) ReadTwoCorrectablePrelim(ctx context.Context, args *ReadRequest) *ReadTwoCorrectablePrelim {
	corr := &ReadTwoCorrectablePrelim{
		level:  LevelNotSet,
		donech: make(chan struct{}),
	}
	go func() {
		c.mgr.readTwoCorrectablePrelim(ctx, c, corr, args)
	}()
	return corr
}

// ReadTwoCorrectable is a reference to a correctable Read quorum call
// with server side preliminary reply support.
type ReadTwoCorrectablePrelim struct {
	mu       sync.Mutex
	reply    *ReadTwoReply
	level    int
	err      error
	done     bool
	watchers []*struct {
		level int
		ch    chan struct{}
	}
	donech chan struct{}
}

// Get returns the reply, level and any error associated with the
// ReadTwoCorrectablePremlim. The method does not block until a (possibly
// itermidiate) reply or error is available. Level is set to LevelNotSet if no
// reply has yet been received. The Done or Watch methods should be used to
// ensure that a reply is available.
func (c *ReadTwoCorrectablePrelim) Get() (*ReadTwoReply, int, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.reply, c.level, c.err
}

// Done returns a channel that's closed when the Read correctable
// quorum call is done. A call is considered done when the quorum function has
// signaled that a quorum of replies was received or that the call returned an
// error.
func (c *ReadTwoCorrectablePrelim) Done() <-chan struct{} {
	return c.donech
}

// Watch returns a channel that's closed when a reply or error at or above the
// specified level is available. If the call is done, the channel is closed
// disregardless of the specified level.
func (c *ReadTwoCorrectablePrelim) Watch(level int) <-chan struct{} {
	ch := make(chan struct{})
	c.mu.Lock()
	if level < c.level {
		close(ch)
		c.mu.Unlock()
		return ch
	}
	c.watchers = append(c.watchers, &struct {
		level int
		ch    chan struct{}
	}{level, ch})
	c.mu.Unlock()
	return ch
}

func (c *ReadTwoCorrectablePrelim) set(reply *ReadTwoReply, level int, err error, done bool) {
	c.mu.Lock()
	if c.done {
		c.mu.Unlock()
		panic("set(...) called on a done correctable")
	}
	c.reply, c.level, c.err, c.done = reply, level, err, done
	if done {
		close(c.donech)
		for _, watcher := range c.watchers {
			if watcher != nil {
				close(watcher.ch)
			}
		}
		c.mu.Unlock()
		return
	}
	for i := range c.watchers {
		if c.watchers[i].level <= level {
			close(c.watchers[i].ch)
			c.watchers[i] = nil
		}
	}
	c.mu.Unlock()
}

// WriteReply encapsulates the reply from a Write quorum call.
// It contains the id of each node of the quorum that replied and a single reply.
type WriteReply struct {
	NodeIDs []uint32
	*WriteResponse
}

func (r WriteReply) String() string {
	return fmt.Sprintf("node ids: %v | answer: %v", r.NodeIDs, r.WriteResponse)
}

// Write invokes a Write quorum call on configuration c
// and returns the result as a WriteReply.
func (c *Configuration) Write(ctx context.Context, args *State) (*WriteReply, error) {
	return c.mgr.write(ctx, c, args)
}

// WriteFuture is a reference to an asynchronous Write quorum call invocation.
type WriteFuture struct {
	reply *WriteReply
	err   error
	c     chan struct{}
}

// WriteFuture asynchronously invokes a Write quorum call
// on configuration c and returns a WriteFuture which can be used to
// inspect the quorum call reply and error when available.
func (c *Configuration) WriteFuture(ctx context.Context, args *State) *WriteFuture {
	f := new(WriteFuture)
	f.c = make(chan struct{}, 1)
	go func() {
		defer close(f.c)
		f.reply, f.err = c.mgr.write(ctx, c, args)
	}()
	return f
}

// Get returns the reply and any error associated with the WriteFuture.
// The method blocks until a reply or error is available.
func (f *WriteFuture) Get() (*WriteReply, error) {
	<-f.c
	return f.reply, f.err
}

// Done reports if a reply and/or error is available for the WriteFuture.
func (f *WriteFuture) Done() bool {
	select {
	case <-f.c:
		return true
	default:
		return false
	}
}

// WriteAsync is a one-way multicast operation, where args is sent to
// every node in configuration c. The call is asynchronous and has no response
// return value.
func (c *Configuration) WriteAsync(ctx context.Context, args *State) error {
	return c.mgr.writeAsync(ctx, c, args)
}
<commit_msg>dev: godoc fix<commit_after>// DO NOT EDIT. Generated by 'gorums' plugin for protoc-gen-go
// Source file to edit is: config_qc_tmpl

package dev

import (
	"fmt"
	"sync"

	"golang.org/x/net/context"
)

// ReadReply encapsulates the reply from a Read quorum call.
// It contains the id of each node of the quorum that replied and a single reply.
type ReadReply struct {
	NodeIDs []uint32
	*State
}

func (r ReadReply) String() string {
	return fmt.Sprintf("node ids: %v | answer: %v", r.NodeIDs, r.State)
}

// Read invokes a Read quorum call on configuration c
// and returns the result as a ReadReply.
func (c *Configuration) Read(ctx context.Context, args *ReadRequest) (*ReadReply, error) {
	return c.mgr.read(ctx, c, args)
}

// ReadFuture is a reference to an asynchronous Read quorum call invocation.
type ReadFuture struct {
	reply *ReadReply
	err   error
	c     chan struct{}
}

// ReadFuture asynchronously invokes a Read quorum call
// on configuration c and returns a ReadFuture which can be used to
// inspect the quorum call reply and error when available.
func (c *Configuration) ReadFuture(ctx context.Context, args *ReadRequest) *ReadFuture {
	f := new(ReadFuture)
	f.c = make(chan struct{}, 1)
	go func() {
		defer close(f.c)
		f.reply, f.err = c.mgr.read(ctx, c, args)
	}()
	return f
}

// Get returns the reply and any error associated with the ReadFuture.
// The method blocks until a reply or error is available.
func (f *ReadFuture) Get() (*ReadReply, error) {
	<-f.c
	return f.reply, f.err
}

// Done reports if a reply and/or error is available for the ReadFuture.
func (f *ReadFuture) Done() bool {
	select {
	case <-f.c:
		return true
	default:
		return false
	}
}

// ReadCorrectable asynchronously invokes a
// correctable Read quorum call on configuration c and returns a
// ReadCorrectable which can be used to inspect any repies or errors
// when available.
func (c *Configuration) ReadCorrectable(ctx context.Context, args *ReadRequest) *ReadCorrectable {
	corr := &ReadCorrectable{
		level:  LevelNotSet,
		donech: make(chan struct{}),
	}
	go func() {
		c.mgr.readCorrectable(ctx, c, corr, args)
	}()
	return corr
}

// ReadCorrectable is a reference to a correctable Read quorum call.
type ReadCorrectable struct {
	mu       sync.Mutex
	reply    *ReadReply
	level    int
	err      error
	done     bool
	watchers []*struct {
		level int
		ch    chan struct{}
	}
	donech chan struct{}
}

// Get returns the reply, level and any error associated with the
// ReadCorrectable. The method does not block until a (possibly
// itermidiate) reply or error is available. Level is set to LevelNotSet if no
// reply has yet been received. The Done or Watch methods should be used to
// ensure that a reply is available.
func (c *ReadCorrectable) Get() (*ReadReply, int, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.reply, c.level, c.err
}

// Done returns a channel that's closed when the Read correctable
// quorum call is done. A call is considered done when the quorum function has
// signaled that a quorum of replies was received or that the call returned an
// error.
func (c *ReadCorrectable) Done() <-chan struct{} {
	return c.donech
}

// Watch returns a channel that's closed when a reply or error at or above the
// specified level is available. If the call is done, the channel is closed
// disregardless of the specified level.
func (c *ReadCorrectable) Watch(level int) <-chan struct{} {
	ch := make(chan struct{})
	c.mu.Lock()
	if level < c.level {
		close(ch)
		c.mu.Unlock()
		return ch
	}
	c.watchers = append(c.watchers, &struct {
		level int
		ch    chan struct{}
	}{level, ch})
	c.mu.Unlock()
	return ch
}

func (c *ReadCorrectable) set(reply *ReadReply, level int, err error, done bool) {
	c.mu.Lock()
	if c.done {
		c.mu.Unlock()
		panic("set(...) called on a done correctable")
	}
	c.reply, c.level, c.err, c.done = reply, level, err, done
	if done {
		close(c.donech)
		for _, watcher := range c.watchers {
			if watcher != nil {
				close(watcher.ch)
			}
		}
		c.mu.Unlock()
		return
	}
	for i := range c.watchers {
		if c.watchers[i].level <= level {
			close(c.watchers[i].ch)
			c.watchers[i] = nil
		}
	}
	c.mu.Unlock()
}

// ReadTwoReply encapsulates the reply from a Read quorum call.
// It contains the id of each node of the quorum that replied and a single reply.
type ReadTwoReply struct {
	NodeIDs []uint32
	*State
}

func (r ReadTwoReply) String() string {
	return fmt.Sprintf("node ids: %v | answer: %v", r.NodeIDs, r.State)
}

// ReadTwoCorrectablePR asynchronously invokes a correctable ReadTwo quorum call
// with server side preliminary reply support on configuration c and returns a
// ReadCorrectable which can be used to inspect any repies or errors
// when available.
func (c *Configuration) ReadTwoCorrectablePrelim(ctx context.Context, args *ReadRequest) *ReadTwoCorrectablePrelim {
	corr := &ReadTwoCorrectablePrelim{
		level:  LevelNotSet,
		donech: make(chan struct{}),
	}
	go func() {
		c.mgr.readTwoCorrectablePrelim(ctx, c, corr, args)
	}()
	return corr
}

// ReadTwoCorrectable is a reference to a correctable Read quorum call
// with server side preliminary reply support.
type ReadTwoCorrectablePrelim struct {
	mu       sync.Mutex
	reply    *ReadTwoReply
	level    int
	err      error
	done     bool
	watchers []*struct {
		level int
		ch    chan struct{}
	}
	donech chan struct{}
}

// Get returns the reply, level and any error associated with the
// ReadTwoCorrectablePremlim. The method does not block until a (possibly
// itermidiate) reply or error is available. Level is set to LevelNotSet if no
// reply has yet been received. The Done or Watch methods should be used to
// ensure that a reply is available.
func (c *ReadTwoCorrectablePrelim) Get() (*ReadTwoReply, int, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.reply, c.level, c.err
}

// Done returns a channel that's closed when the Read correctable
// quorum call is done. A call is considered done when the quorum function has
// signaled that a quorum of replies was received or that the call returned an
// error.
func (c *ReadTwoCorrectablePrelim) Done() <-chan struct{} {
	return c.donech
}

// Watch returns a channel that's closed when a reply or error at or above the
// specified level is available. If the call is done, the channel is closed
// disregardless of the specified level.
func (c *ReadTwoCorrectablePrelim) Watch(level int) <-chan struct{} {
	ch := make(chan struct{})
	c.mu.Lock()
	if level < c.level {
		close(ch)
		c.mu.Unlock()
		return ch
	}
	c.watchers = append(c.watchers, &struct {
		level int
		ch    chan struct{}
	}{level, ch})
	c.mu.Unlock()
	return ch
}

func (c *ReadTwoCorrectablePrelim) set(reply *ReadTwoReply, level int, err error, done bool) {
	c.mu.Lock()
	if c.done {
		c.mu.Unlock()
		panic("set(...) called on a done correctable")
	}
	c.reply, c.level, c.err, c.done = reply, level, err, done
	if done {
		close(c.donech)
		for _, watcher := range c.watchers {
			if watcher != nil {
				close(watcher.ch)
			}
		}
		c.mu.Unlock()
		return
	}
	for i := range c.watchers {
		if c.watchers[i].level <= level {
			close(c.watchers[i].ch)
			c.watchers[i] = nil
		}
	}
	c.mu.Unlock()
}

// WriteReply encapsulates the reply from a Write quorum call.
// It contains the id of each node of the quorum that replied and a single reply.
type WriteReply struct {
	NodeIDs []uint32
	*WriteResponse
}

func (r WriteReply) String() string {
	return fmt.Sprintf("node ids: %v | answer: %v", r.NodeIDs, r.WriteResponse)
}

// Write invokes a Write quorum call on configuration c
// and returns the result as a WriteReply.
func (c *Configuration) Write(ctx context.Context, args *State) (*WriteReply, error) {
	return c.mgr.write(ctx, c, args)
}

// WriteFuture is a reference to an asynchronous Write quorum call invocation.
type WriteFuture struct {
	reply *WriteReply
	err   error
	c     chan struct{}
}

// WriteFuture asynchronously invokes a Write quorum call
// on configuration c and returns a WriteFuture which can be used to
// inspect the quorum call reply and error when available.
func (c *Configuration) WriteFuture(ctx context.Context, args *State) *WriteFuture {
	f := new(WriteFuture)
	f.c = make(chan struct{}, 1)
	go func() {
		defer close(f.c)
		f.reply, f.err = c.mgr.write(ctx, c, args)
	}()
	return f
}

// Get returns the reply and any error associated with the WriteFuture.
// The method blocks until a reply or error is available.
func (f *WriteFuture) Get() (*WriteReply, error) {
	<-f.c
	return f.reply, f.err
}

// Done reports if a reply and/or error is available for the WriteFuture.
func (f *WriteFuture) Done() bool {
	select {
	case <-f.c:
		return true
	default:
		return false
	}
}

// WriteAsync is a one-way multicast operation, where args is sent to
// every node in configuration c. The call is asynchronous and has no response
// return value.
func (c *Configuration) WriteAsync(ctx context.Context, args *State) error {
	return c.mgr.writeAsync(ctx, c, args)
}
<|endoftext|>


<commit_before>package dictionary

import (
	"bufio"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type File interface {
	io.Reader
	io.Closer
	Name() string
}

type Opener interface {
	OpenFile(name string) (File, error)
}

type FileSystemOpener struct {
}

func (f *FileSystemOpener) OpenFile(name string) (File, error) {
	absPath, err := filepath.Abs(name)
	if err != nil {
		return nil, err
	}
	file, err := os.Open(absPath)
	return file, err
}

type Parser struct {
	Opener Opener
}

func (p *Parser) Parse(f File) (*Dictionary, error) {
	parsedFiles := map[string]struct{}{
		f.Name(): {},
	}
	dict := new(Dictionary)
	if err := p.parse(dict, parsedFiles, f); err != nil {
		return nil, err
	}
	return dict, nil
}

func (p *Parser) parse(dict *Dictionary, parsedFiles map[string]struct{}, f File) error {
	s := bufio.NewScanner(f)

	var vendorBlock *Vendor

	lineNo := 1
	for ; s.Scan(); lineNo++ {
		line := s.Text()
		if idx := strings.IndexByte(line, '#'); idx >= 0 {
			line = line[:idx]
		}
		if len(line) == 0 {
			continue
		}

		fields := strings.Fields(line)
		switch {
		case (len(fields) == 4 || len(fields) == 5) && fields[0] == "ATTRIBUTE":
			attr, err := p.parseAttribute(fields)
			if err != nil {
				return &ParseError{
					Inner: err,
					File:  f,
					Line:  lineNo,
				}
			}

			var existing *Attribute
			if vendorBlock == nil {
				existing = dict.AttributeByName(attr.Name)
			} else {
				existing = vendorBlock.AttributeByName(attr.Name)
			}
			if existing != nil {
				return &ParseError{
					Inner: &DuplicateAttributeError{
						Attribute: attr,
					},
					File: f,
					Line: lineNo,
				}
			}

			if vendorBlock == nil {
				dict.Attributes = append(dict.Attributes, attr)
			} else {
				vendorBlock.Attributes = append(vendorBlock.Attributes, attr)
			}

		case len(fields) == 4 && fields[0] == "VALUE":
			value, err := p.parseValue(fields)
			if err != nil {
				return &ParseError{
					Inner: err,
					File:  f,
					Line:  lineNo,
				}
			}

			// no duplicate check; VALUEs can be overwritten

			if vendorBlock == nil {
				dict.Values = append(dict.Values, value)
			} else {
				vendorBlock.Values = append(vendorBlock.Values, value)
			}

		case (len(fields) == 3 && len(fields) == 4) && fields[0] == "VENDOR":
			vendor, err := p.parseVendor(fields)
			if err != nil {
				return &ParseError{
					Inner: err,
					File:  f,
					Line:  lineNo,
				}
			}

			if existing := dict.vendorByNameOrNumber(vendor.Name, vendor.Number); existing != nil {
				return &ParseError{
					Inner: &DuplicateVendorError{
						Vendor: vendor,
					},
					File: f,
					Line: lineNo,
				}
			}

			dict.Vendors = append(dict.Vendors, vendor)

		case len(fields) == 2 && fields[0] == "BEGIN-VENDOR":
			// TODO: support RFC 6929 extended VSA?

			if vendorBlock != nil {
				return &ParseError{
					Inner: &NestedVendorBlockError{},
					File:  f,
					Line:  lineNo,
				}
			}

			vendor := dict.VendorByName(fields[1])
			if vendor == nil {
				return &ParseError{
					Inner: &UnknownVendorError{
						Vendor: fields[1],
					},
					File: f,
					Line: lineNo,
				}
			}

			vendorBlock = vendor

		case len(fields) == 2 && fields[0] == "END-VENDOR":
			if vendorBlock == nil {
				return &ParseError{
					Inner: &UnmatchedEndVendorError{},
					File:  f,
					Line:  lineNo,
				}
			}
			if vendorBlock.Name != fields[1] {
				return &ParseError{
					Inner: &InvalidEndVendorError{
						Vendor: fields[1],
					},
					File: f,
					Line: lineNo,
				}
			}

			vendorBlock = nil

		case len(fields) == 2 && fields[0] == "$INCLUDE":
			if vendorBlock != nil {
				return &ParseError{
					Inner: &BeginVendorIncludeError{},
					File:  f,
					Line:  lineNo,
				}
			}

			err := func() error {
				incFile, err := p.Opener.OpenFile(fields[1])
				if err != nil {
					return &ParseError{
						Inner: err,
						File:  f,
						Line:  lineNo,
					}
				}
				defer incFile.Close()

				incFileName := incFile.Name()
				if _, included := parsedFiles[incFileName]; included {
					return &ParseError{
						Inner: &RecursiveIncludeError{
							Filename: incFileName,
						},
						File: f,
						Line: lineNo,
					}
				}

				if err := p.parse(dict, parsedFiles, incFile); err != nil {
					return err
				}

				if err := incFile.Close(); err != nil {
					return &ParseError{
						Inner: err,
						File:  f,
						Line:  lineNo,
					}
				}

				return nil
			}()
			if err != nil {
				return err
			}

		default:
			return &ParseError{
				Inner: &UnknownLineError{
					Line: s.Text(),
				},
				File: f,
				Line: lineNo,
			}
		}
	}

	if err := s.Err(); err != nil {
		return err
	}

	if vendorBlock != nil {
		return &ParseError{
			Inner: &UnclosedVendorBlockError{},
			File:  f,
			Line:  lineNo - 1,
		}
	}

	return nil
}

func (p *Parser) ParseFile(filename string) (*Dictionary, error) {
	f, err := p.Opener.OpenFile(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return p.Parse(f)
}

func (p *Parser) parseAttribute(f []string) (*Attribute, error) {
	// 4 <= len(f) <= 5

	attr := &Attribute{
		Name: f[1],
		OID:  f[2],
	}

	switch {
	case f[3] == "string":
		attr.Type = AttributeString
	case f[3] == "octets":
		attr.Type = AttributeOctets
	case strings.HasPrefix(f[3], "octets[") && strings.HasSuffix(f[3], "]") && len(f[3]) > 8:
		size, err := strconv.ParseInt(f[3][7:len(f[3])-1], 10, 32)
		if err != nil {
			return nil, &UnknownAttributeTypeError{
				Type: f[3],
			}
		}
		attr.Size = new(int)
		*attr.Size = int(size)
		attr.Type = AttributeOctets
	case f[3] == "ipaddr":
		attr.Type = AttributeIPAddr
	case f[3] == "date":
		attr.Type = AttributeDate
	case f[3] == "integer":
		attr.Type = AttributeInteger
	case f[3] == "ipv6addr":
		attr.Type = AttributeIPv6Addr
	case f[3] == "ipv6prefix":
		attr.Type = AttributeIPv6Prefix
	case f[3] == "ifid":
		attr.Type = AttributeIFID
	case f[3] == "integer64":
		attr.Type = AttributeInteger64
	case f[3] == "vsa":
		attr.Type = AttributeVSA
	default:
		return nil, &UnknownAttributeTypeError{
			Type: f[3],
		}
	}

	if len(f) >= 5 {
		flags := strings.Split(f[4], ",")
		for _, f := range flags {
			switch {
			case strings.HasPrefix(f, "encrypt="):
				if attr.FlagEncrypt != nil {
					return nil, &DuplicateAttributeFlagError{
						Flag: f,
					}
				}
				encryptTypeStr := strings.TrimPrefix(f, "encrypt=")
				encryptType, err := strconv.ParseInt(encryptTypeStr, 10, 32)
				if err != nil {
					return nil, &InvalidAttributeEncryptTypeError{
						Type: encryptTypeStr,
					}
				}
				attr.FlagEncrypt = new(int)
				*attr.FlagEncrypt = int(encryptType)
			case f == "has_tag":
				if attr.FlagHasTag != nil {
					return nil, &DuplicateAttributeFlagError{
						Flag: f,
					}
				}
				attr.FlagHasTag = new(bool)
				*attr.FlagHasTag = true
			case f == "concat":
				if attr.FlagConcat != nil {
					return nil, &DuplicateAttributeFlagError{
						Flag: f,
					}
				}
				attr.FlagConcat = new(bool)
				*attr.FlagConcat = true
			default:
				return nil, &UnknownAttributeFlagError{
					Flag: f,
				}
			}
		}
	}

	return attr, nil
}

func (p *Parser) parseValue(f []string) (*Value, error) {
	// len(f) == 4

	value := &Value{
		Attribute: f[1],
		Name:      f[2],
	}

	number, err := strconv.ParseInt(f[3], 10, 32)
	if err != nil {
		return nil, err
	}
	value.Number = int(number)

	return value, nil
}

func (p *Parser) parseVendor(f []string) (*Vendor, error) {
	// 3 <= len(f) <= 4

	number, err := strconv.ParseInt(f[2], 10, 32)
	if err != nil {
		return nil, err
	}

	vendor := &Vendor{
		Name:         f[1],
		Number:       int(number),
		TypeOctets:   1,
		LengthOctets: 1,
	}

	if len(f) == 4 {
		// "format=t,l"
		// t ∈ [1, 2, 4]
		// l ∈ [0, 1, 2]
		if !strings.HasPrefix(f[3], "format=") || len(f[3]) != 10 || f[3][8] != ',' || (f[3][7] != '1' && f[3][7] != '2' && f[3][7] != '4') || (f[3][9] < '0' && f[3][9] > '2') {
			return nil, &InvalidVendorFormatError{
				Format: f[3],
			}
		}
		vendor.TypeOctets = int(f[3][7] - '0')
		vendor.LengthOctets = int(f[3][9] - '0')
	}

	return vendor, nil
}
<commit_msg>fix parser erroring on VENDOR lines<commit_after>package dictionary

import (
	"bufio"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type File interface {
	io.Reader
	io.Closer
	Name() string
}

type Opener interface {
	OpenFile(name string) (File, error)
}

type FileSystemOpener struct {
}

func (f *FileSystemOpener) OpenFile(name string) (File, error) {
	absPath, err := filepath.Abs(name)
	if err != nil {
		return nil, err
	}
	file, err := os.Open(absPath)
	return file, err
}

type Parser struct {
	Opener Opener
}

func (p *Parser) Parse(f File) (*Dictionary, error) {
	parsedFiles := map[string]struct{}{
		f.Name(): {},
	}
	dict := new(Dictionary)
	if err := p.parse(dict, parsedFiles, f); err != nil {
		return nil, err
	}
	return dict, nil
}

func (p *Parser) parse(dict *Dictionary, parsedFiles map[string]struct{}, f File) error {
	s := bufio.NewScanner(f)

	var vendorBlock *Vendor

	lineNo := 1
	for ; s.Scan(); lineNo++ {
		line := s.Text()
		if idx := strings.IndexByte(line, '#'); idx >= 0 {
			line = line[:idx]
		}
		if len(line) == 0 {
			continue
		}

		fields := strings.Fields(line)
		switch {
		case (len(fields) == 4 || len(fields) == 5) && fields[0] == "ATTRIBUTE":
			attr, err := p.parseAttribute(fields)
			if err != nil {
				return &ParseError{
					Inner: err,
					File:  f,
					Line:  lineNo,
				}
			}

			var existing *Attribute
			if vendorBlock == nil {
				existing = dict.AttributeByName(attr.Name)
			} else {
				existing = vendorBlock.AttributeByName(attr.Name)
			}
			if existing != nil {
				return &ParseError{
					Inner: &DuplicateAttributeError{
						Attribute: attr,
					},
					File: f,
					Line: lineNo,
				}
			}

			if vendorBlock == nil {
				dict.Attributes = append(dict.Attributes, attr)
			} else {
				vendorBlock.Attributes = append(vendorBlock.Attributes, attr)
			}

		case len(fields) == 4 && fields[0] == "VALUE":
			value, err := p.parseValue(fields)
			if err != nil {
				return &ParseError{
					Inner: err,
					File:  f,
					Line:  lineNo,
				}
			}

			// no duplicate check; VALUEs can be overwritten

			if vendorBlock == nil {
				dict.Values = append(dict.Values, value)
			} else {
				vendorBlock.Values = append(vendorBlock.Values, value)
			}

		case (len(fields) == 3 || len(fields) == 4) && fields[0] == "VENDOR":
			vendor, err := p.parseVendor(fields)
			if err != nil {
				return &ParseError{
					Inner: err,
					File:  f,
					Line:  lineNo,
				}
			}

			if existing := dict.vendorByNameOrNumber(vendor.Name, vendor.Number); existing != nil {
				return &ParseError{
					Inner: &DuplicateVendorError{
						Vendor: vendor,
					},
					File: f,
					Line: lineNo,
				}
			}

			dict.Vendors = append(dict.Vendors, vendor)

		case len(fields) == 2 && fields[0] == "BEGIN-VENDOR":
			// TODO: support RFC 6929 extended VSA?

			if vendorBlock != nil {
				return &ParseError{
					Inner: &NestedVendorBlockError{},
					File:  f,
					Line:  lineNo,
				}
			}

			vendor := dict.VendorByName(fields[1])
			if vendor == nil {
				return &ParseError{
					Inner: &UnknownVendorError{
						Vendor: fields[1],
					},
					File: f,
					Line: lineNo,
				}
			}

			vendorBlock = vendor

		case len(fields) == 2 && fields[0] == "END-VENDOR":
			if vendorBlock == nil {
				return &ParseError{
					Inner: &UnmatchedEndVendorError{},
					File:  f,
					Line:  lineNo,
				}
			}
			if vendorBlock.Name != fields[1] {
				return &ParseError{
					Inner: &InvalidEndVendorError{
						Vendor: fields[1],
					},
					File: f,
					Line: lineNo,
				}
			}

			vendorBlock = nil

		case len(fields) == 2 && fields[0] == "$INCLUDE":
			if vendorBlock != nil {
				return &ParseError{
					Inner: &BeginVendorIncludeError{},
					File:  f,
					Line:  lineNo,
				}
			}

			err := func() error {
				incFile, err := p.Opener.OpenFile(fields[1])
				if err != nil {
					return &ParseError{
						Inner: err,
						File:  f,
						Line:  lineNo,
					}
				}
				defer incFile.Close()

				incFileName := incFile.Name()
				if _, included := parsedFiles[incFileName]; included {
					return &ParseError{
						Inner: &RecursiveIncludeError{
							Filename: incFileName,
						},
						File: f,
						Line: lineNo,
					}
				}

				if err := p.parse(dict, parsedFiles, incFile); err != nil {
					return err
				}

				if err := incFile.Close(); err != nil {
					return &ParseError{
						Inner: err,
						File:  f,
						Line:  lineNo,
					}
				}

				return nil
			}()
			if err != nil {
				return err
			}

		default:
			return &ParseError{
				Inner: &UnknownLineError{
					Line: s.Text(),
				},
				File: f,
				Line: lineNo,
			}
		}
	}

	if err := s.Err(); err != nil {
		return err
	}

	if vendorBlock != nil {
		return &ParseError{
			Inner: &UnclosedVendorBlockError{},
			File:  f,
			Line:  lineNo - 1,
		}
	}

	return nil
}

func (p *Parser) ParseFile(filename string) (*Dictionary, error) {
	f, err := p.Opener.OpenFile(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return p.Parse(f)
}

func (p *Parser) parseAttribute(f []string) (*Attribute, error) {
	// 4 <= len(f) <= 5

	attr := &Attribute{
		Name: f[1],
		OID:  f[2],
	}

	switch {
	case f[3] == "string":
		attr.Type = AttributeString
	case f[3] == "octets":
		attr.Type = AttributeOctets
	case strings.HasPrefix(f[3], "octets[") && strings.HasSuffix(f[3], "]") && len(f[3]) > 8:
		size, err := strconv.ParseInt(f[3][7:len(f[3])-1], 10, 32)
		if err != nil {
			return nil, &UnknownAttributeTypeError{
				Type: f[3],
			}
		}
		attr.Size = new(int)
		*attr.Size = int(size)
		attr.Type = AttributeOctets
	case f[3] == "ipaddr":
		attr.Type = AttributeIPAddr
	case f[3] == "date":
		attr.Type = AttributeDate
	case f[3] == "integer":
		attr.Type = AttributeInteger
	case f[3] == "ipv6addr":
		attr.Type = AttributeIPv6Addr
	case f[3] == "ipv6prefix":
		attr.Type = AttributeIPv6Prefix
	case f[3] == "ifid":
		attr.Type = AttributeIFID
	case f[3] == "integer64":
		attr.Type = AttributeInteger64
	case f[3] == "vsa":
		attr.Type = AttributeVSA
	default:
		return nil, &UnknownAttributeTypeError{
			Type: f[3],
		}
	}

	if len(f) >= 5 {
		flags := strings.Split(f[4], ",")
		for _, f := range flags {
			switch {
			case strings.HasPrefix(f, "encrypt="):
				if attr.FlagEncrypt != nil {
					return nil, &DuplicateAttributeFlagError{
						Flag: f,
					}
				}
				encryptTypeStr := strings.TrimPrefix(f, "encrypt=")
				encryptType, err := strconv.ParseInt(encryptTypeStr, 10, 32)
				if err != nil {
					return nil, &InvalidAttributeEncryptTypeError{
						Type: encryptTypeStr,
					}
				}
				attr.FlagEncrypt = new(int)
				*attr.FlagEncrypt = int(encryptType)
			case f == "has_tag":
				if attr.FlagHasTag != nil {
					return nil, &DuplicateAttributeFlagError{
						Flag: f,
					}
				}
				attr.FlagHasTag = new(bool)
				*attr.FlagHasTag = true
			case f == "concat":
				if attr.FlagConcat != nil {
					return nil, &DuplicateAttributeFlagError{
						Flag: f,
					}
				}
				attr.FlagConcat = new(bool)
				*attr.FlagConcat = true
			default:
				return nil, &UnknownAttributeFlagError{
					Flag: f,
				}
			}
		}
	}

	return attr, nil
}

func (p *Parser) parseValue(f []string) (*Value, error) {
	// len(f) == 4

	value := &Value{
		Attribute: f[1],
		Name:      f[2],
	}

	number, err := strconv.ParseInt(f[3], 10, 32)
	if err != nil {
		return nil, err
	}
	value.Number = int(number)

	return value, nil
}

func (p *Parser) parseVendor(f []string) (*Vendor, error) {
	// 3 <= len(f) <= 4

	number, err := strconv.ParseInt(f[2], 10, 32)
	if err != nil {
		return nil, err
	}

	vendor := &Vendor{
		Name:         f[1],
		Number:       int(number),
		TypeOctets:   1,
		LengthOctets: 1,
	}

	if len(f) == 4 {
		// "format=t,l"
		// t ∈ [1, 2, 4]
		// l ∈ [0, 1, 2]
		if !strings.HasPrefix(f[3], "format=") || len(f[3]) != 10 || f[3][8] != ',' || (f[3][7] != '1' && f[3][7] != '2' && f[3][7] != '4') || (f[3][9] < '0' && f[3][9] > '2') {
			return nil, &InvalidVendorFormatError{
				Format: f[3],
			}
		}
		vendor.TypeOctets = int(f[3][7] - '0')
		vendor.LengthOctets = int(f[3][9] - '0')
	}

	return vendor, nil
}
<|endoftext|>