<commit_before>package mongo

import (
	"fmt"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"os"
	"time"

	"github.com/marcusolsson/goddd/cargo"
	"github.com/marcusolsson/goddd/location"
	"github.com/marcusolsson/goddd/voyage"
)

type cargoRepository struct {
	db      string
	session *mgo.Session
}

func timeTrack(start time.Time, name string) {
	elapsed := time.Since(start)
	fmt.Printf("%s took %s", name, elapsed)
}

type timeCargoRepository struct {
	cargo *cargoRepository
}

func (r *timeCargoRepository) Remove(cargo *cargo.Cargo) error {
	defer timeTrack(time.Now(), "cargo Remove")
	return r.cargo.Remove(cargo)
}

func (r *cargoRepository) Remove(cargo *cargo.Cargo) error {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	err := c.Remove(bson.M{"trackingid": cargo.TrackingID})

	return err
}

func (r *timeCargoRepository) Store(cargo *cargo.Cargo) error {
	defer timeTrack(time.Now(), "cargo Store")
	return r.cargo.Store(cargo)
}

func (r *cargoRepository) Store(cargo *cargo.Cargo) error {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	_, err := c.Upsert(bson.M{"trackingid": cargo.TrackingID}, bson.M{"$set": cargo})

	return err
}

func (r *timeCargoRepository) Find(id cargo.TrackingID) (*cargo.Cargo, error) {
	defer timeTrack(time.Now(), "cargo Find")
	return r.cargo.Find(id)
}

func (r *cargoRepository) Find(id cargo.TrackingID) (*cargo.Cargo, error) {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	var result cargo.Cargo
	if err := c.Find(bson.M{"trackingid": id}).One(&result); err != nil {
		if err == mgo.ErrNotFound {
			return nil, cargo.ErrUnknown
		}
		return nil, err
	}

	return &result, nil
}

func (r *timeCargoRepository) FindAll() []*cargo.Cargo {
	defer timeTrack(time.Now(), "cargo FindAll")
	return r.cargo.FindAll()
}

func (r *cargoRepository) FindAll() []*cargo.Cargo {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	var result []*cargo.Cargo
	if err := c.Find(bson.M{}).All(&result); err != nil {
		return []*cargo.Cargo{}
	}

	return result
}

// NewCargoRepository returns a new instance of a MongoDB cargo repository.
func NewCargoRepository(db string, session *mgo.Session) (cargo.Repository, error) {
	r := &cargoRepository{
		db:      db,
		session: session,
	}

	index := mgo.Index{
		Key:        []string{"trackingid"},
		Unique:     true,
		DropDups:   true,
		Background: true,
		Sparse:     true,
	}

	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	if err := c.EnsureIndex(index); err != nil {
		return nil, err
	}

	if os.Getenv("TIME_MONGO") == "1" {
		return &timeCargoRepository{
			cargo: r,
		}, nil
	}

	return r, nil
}

type locationRepository struct {
	db      string
	session *mgo.Session
}

type timeLocationRepository struct {
	location *locationRepository
}

func (r *timeLocationRepository) Find(locode location.UNLocode) (*location.Location, error) {
	defer timeTrack(time.Now(), "location Find")
	return r.location.Find(locode)
}

func (r *locationRepository) Find(locode location.UNLocode) (*location.Location, error) {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("location")

	var result location.Location
	if err := c.Find(bson.M{"unlocode": locode}).One(&result); err != nil {
		if err == mgo.ErrNotFound {
			return nil, location.ErrUnknown
		}
		return nil, err
	}

	return &result, nil
}

func (r *timeLocationRepository) FindAll() []*location.Location {
	defer timeTrack(time.Now(), "location FindAll")
	return r.location.FindAll()
}

func (r *locationRepository) FindAll() []*location.Location {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("location")

	var result []*location.Location
	if err := c.Find(bson.M{}).All(&result); err != nil {
		return []*location.Location{}
	}

	return result
}

func (r *locationRepository) store(l *location.Location) error {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("location")

	_, err := c.Upsert(bson.M{"unlocode": l.UNLocode}, bson.M{"$set": l})

	return err
}

// NewLocationRepository returns a new instance of a MongoDB location repository.
func NewLocationRepository(db string, session *mgo.Session) (location.Repository, error) {
	r := &locationRepository{
		db:      db,
		session: session,
	}

	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("location")

	index := mgo.Index{
		Key:        []string{"unlocode"},
		Unique:     true,
		DropDups:   true,
		Background: true,
		Sparse:     true,
	}

	if err := c.EnsureIndex(index); err != nil {
		return nil, err
	}

	initial := []*location.Location{
		location.Stockholm,
		location.Melbourne,
		location.Hongkong,
		location.Tokyo,
		location.Rotterdam,
		location.Hamburg,
	}

	for _, l := range initial {
		r.store(l)
	}

	if os.Getenv("TIME_MONGO") == "1" {
		return &timeLocationRepository{
			location: r,
		}, nil
	}

	return r, nil
}

type voyageRepository struct {
	db      string
	session *mgo.Session
}

type timeVoyageRepository struct {
	voyage *voyageRepository
}

func (r *timeVoyageRepository) Find(voyageNumber voyage.Number) (*voyage.Voyage, error) {
	defer timeTrack(time.Now(), "voyage Find")
	return r.voyage.Find(voyageNumber)
}

func (r *voyageRepository) Find(voyageNumber voyage.Number) (*voyage.Voyage, error) {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("voyage")

	var result voyage.Voyage
	if err := c.Find(bson.M{"number": voyageNumber}).One(&result); err != nil {
		if err == mgo.ErrNotFound {
			return nil, voyage.ErrUnknown
		}
		return nil, err
	}

	return &result, nil
}

func (r *voyageRepository) store(v *voyage.Voyage) error {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("voyage")

	_, err := c.Upsert(bson.M{"number": v.Number}, bson.M{"$set": v})

	return err
}

// NewVoyageRepository returns a new instance of a MongoDB voyage repository.
func NewVoyageRepository(db string, session *mgo.Session) (voyage.Repository, error) {
	r := &voyageRepository{
		db:      db,
		session: session,
	}

	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("voyage")

	index := mgo.Index{
		Key:        []string{"number"},
		Unique:     true,
		DropDups:   true,
		Background: true,
		Sparse:     true,
	}

	if err := c.EnsureIndex(index); err != nil {
		return nil, err
	}

	initial := []*voyage.Voyage{
		voyage.V100,
		voyage.V300,
		voyage.V400,
		voyage.V0100S,
		voyage.V0200T,
		voyage.V0300A,
		voyage.V0301S,
		voyage.V0400S,
	}

	for _, v := range initial {
		r.store(v)
	}

	if os.Getenv("TIME_MONGO") == "1" {
		return &timeVoyageRepository{
			voyage: r,
		}, nil
	}

	return r, nil
}

type handlingEventRepository struct {
	db      string
	session *mgo.Session
}

type timeHandlingEventRepository struct {
	event *handlingEventRepository
}

func (r *timeHandlingEventRepository) Store(e cargo.HandlingEvent) {
	defer timeTrack(time.Now(), "event Store")
	r.event.Store(e)
}

func (r *handlingEventRepository) Store(e cargo.HandlingEvent) {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("handling_event")

	_ = c.Insert(e)
}

func (r *timeHandlingEventRepository) QueryHandlingHistory(id cargo.TrackingID) cargo.HandlingHistory {
	defer timeTrack(time.Now(), "event QueryHandlingHistory")
	return r.event.QueryHandlingHistory(id)
}

func (r *handlingEventRepository) QueryHandlingHistory(id cargo.TrackingID) cargo.HandlingHistory {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("handling_event")

	var result []cargo.HandlingEvent
	_ = c.Find(bson.M{"trackingid": id}).All(&result)

	return cargo.HandlingHistory{HandlingEvents: result}
}

// NewHandlingEventRepository returns a new instance of a MongoDB handling event repository.
func NewHandlingEventRepository(db string, session *mgo.Session) cargo.HandlingEventRepository {
	r := &handlingEventRepository{
		db:      db,
		session: session,
	}

	if os.Getenv("TIME_MONGO") == "1" {
		return &timeHandlingEventRepository{
			event: r,
		}
	}

	return r
}
<commit_msg>Revert "Time mongo queries"<commit_after>package mongo

import (
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"github.com/marcusolsson/goddd/cargo"
	"github.com/marcusolsson/goddd/location"
	"github.com/marcusolsson/goddd/voyage"
)

type cargoRepository struct {
	db      string
	session *mgo.Session
}

func (r *cargoRepository) Remove(cargo *cargo.Cargo) error {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	err := c.Remove(bson.M{"trackingid": cargo.TrackingID})

	return err
}

func (r *cargoRepository) Store(cargo *cargo.Cargo) error {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	_, err := c.Upsert(bson.M{"trackingid": cargo.TrackingID}, bson.M{"$set": cargo})

	return err
}

func (r *cargoRepository) Find(id cargo.TrackingID) (*cargo.Cargo, error) {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	var result cargo.Cargo
	if err := c.Find(bson.M{"trackingid": id}).One(&result); err != nil {
		if err == mgo.ErrNotFound {
			return nil, cargo.ErrUnknown
		}
		return nil, err
	}

	return &result, nil
}

func (r *cargoRepository) FindAll() []*cargo.Cargo {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	var result []*cargo.Cargo
	if err := c.Find(bson.M{}).All(&result); err != nil {
		return []*cargo.Cargo{}
	}

	return result
}

// NewCargoRepository returns a new instance of a MongoDB cargo repository.
func NewCargoRepository(db string, session *mgo.Session) (cargo.Repository, error) {
	r := &cargoRepository{
		db:      db,
		session: session,
	}

	index := mgo.Index{
		Key:        []string{"trackingid"},
		Unique:     true,
		DropDups:   true,
		Background: true,
		Sparse:     true,
	}

	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("cargo")

	if err := c.EnsureIndex(index); err != nil {
		return nil, err
	}

	return r, nil
}

type locationRepository struct {
	db      string
	session *mgo.Session
}

func (r *locationRepository) Find(locode location.UNLocode) (*location.Location, error) {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("location")

	var result location.Location
	if err := c.Find(bson.M{"unlocode": locode}).One(&result); err != nil {
		if err == mgo.ErrNotFound {
			return nil, location.ErrUnknown
		}
		return nil, err
	}

	return &result, nil
}

func (r *locationRepository) FindAll() []*location.Location {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("location")

	var result []*location.Location
	if err := c.Find(bson.M{}).All(&result); err != nil {
		return []*location.Location{}
	}

	return result
}

func (r *locationRepository) store(l *location.Location) error {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("location")

	_, err := c.Upsert(bson.M{"unlocode": l.UNLocode}, bson.M{"$set": l})

	return err
}

// NewLocationRepository returns a new instance of a MongoDB location repository.
func NewLocationRepository(db string, session *mgo.Session) (location.Repository, error) {
	r := &locationRepository{
		db:      db,
		session: session,
	}

	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("location")

	index := mgo.Index{
		Key:        []string{"unlocode"},
		Unique:     true,
		DropDups:   true,
		Background: true,
		Sparse:     true,
	}

	if err := c.EnsureIndex(index); err != nil {
		return nil, err
	}

	initial := []*location.Location{
		location.Stockholm,
		location.Melbourne,
		location.Hongkong,
		location.Tokyo,
		location.Rotterdam,
		location.Hamburg,
	}

	for _, l := range initial {
		r.store(l)
	}

	return r, nil
}

type voyageRepository struct {
	db      string
	session *mgo.Session
}

func (r *voyageRepository) Find(voyageNumber voyage.Number) (*voyage.Voyage, error) {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("voyage")

	var result voyage.Voyage
	if err := c.Find(bson.M{"number": voyageNumber}).One(&result); err != nil {
		if err == mgo.ErrNotFound {
			return nil, voyage.ErrUnknown
		}
		return nil, err
	}

	return &result, nil
}

func (r *voyageRepository) store(v *voyage.Voyage) error {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("voyage")

	_, err := c.Upsert(bson.M{"number": v.Number}, bson.M{"$set": v})

	return err
}

// NewVoyageRepository returns a new instance of a MongoDB voyage repository.
func NewVoyageRepository(db string, session *mgo.Session) (voyage.Repository, error) {
	r := &voyageRepository{
		db:      db,
		session: session,
	}

	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("voyage")

	index := mgo.Index{
		Key:        []string{"number"},
		Unique:     true,
		DropDups:   true,
		Background: true,
		Sparse:     true,
	}

	if err := c.EnsureIndex(index); err != nil {
		return nil, err
	}

	initial := []*voyage.Voyage{
		voyage.V100,
		voyage.V300,
		voyage.V400,
		voyage.V0100S,
		voyage.V0200T,
		voyage.V0300A,
		voyage.V0301S,
		voyage.V0400S,
	}

	for _, v := range initial {
		r.store(v)
	}

	return r, nil
}

type handlingEventRepository struct {
	db      string
	session *mgo.Session
}

func (r *handlingEventRepository) Store(e cargo.HandlingEvent) {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("handling_event")

	_ = c.Insert(e)
}

func (r *handlingEventRepository) QueryHandlingHistory(id cargo.TrackingID) cargo.HandlingHistory {
	sess := r.session.Copy()
	defer sess.Close()

	c := sess.DB(r.db).C("handling_event")

	var result []cargo.HandlingEvent
	_ = c.Find(bson.M{"trackingid": id}).All(&result)

	return cargo.HandlingHistory{HandlingEvents: result}
}

// NewHandlingEventRepository returns a new instance of a MongoDB handling event repository.
func NewHandlingEventRepository(db string, session *mgo.Session) cargo.HandlingEventRepository {
	return &handlingEventRepository{
		db:      db,
		session: session,
	}
}
<|endoftext|>


<commit_before>package arena

import (
	"encoding/json"
	"fmt"
	"io"
	"math/rand"
	"time"

	"github.com/asciimoo/solary/arena/board"
	"github.com/asciimoo/solary/arena/coord"
	"github.com/asciimoo/solary/player"
)

const (
	ROUND_TIMEOUT = 2
	MAX_ROUNDS    = 500
)

var battle_id uint = 0
var player_id uint = 0

type Arena struct {
	Id      uint
	Round   uint
	Players []*player.Player
	Board   *board.Board
}

func Create() *Arena {
	a := &Arena{
		battle_id,
		0,
		make([]*player.Player, 0),
		board.Create(),
	}
	battle_id += 1
	return a
}

func (a *Arena) Play() {
	ch := make(chan *player.Move)
	for _, p := range a.Players {
		defer p.Conn.Close()
		go p.Read(ch)
	}
	a.setSpawnPos()
	for {
		a.Round += 1
		a.broadcastStatus()
		if a.Round == MAX_ROUNDS || a.getActivePlayersNum() == 0 {
			fmt.Println("Game", a.Id, "finished")
			return
		}
		if a.Round%100 == 0 {
			a.Board.PopulateRandomLoot()
		}
		// collect moves
		moves := a.getMoves(ch)
		// activate laser beams and traps
		for _, move := range moves {
			if move.Item == "" {
				continue
			}
			c, ok := move.Player.Inventory[move.Item]
			if !ok || c <= 0 {
				continue
			}
			move.Player.Inventory[move.Item] -= 1
			switch move.Item {
			case "trap":
				a.Board.FieldByCoord(move.Player.Position).AddTrap()
			case "laser beam":
				laser_x := 0
				laser_y := 0
				switch move.Direction {
				case "up":
					laser_y -= 1
				case "down":
					laser_y += 1
				case "left":
					laser_x -= 1
				case "right":
					laser_x += 1
				}
				for i := 1; i <= 2; i++ {
					laser_coord := coord.Coord{
						uint(laser_x*i) + move.Player.Position.X,
						uint(laser_y*i) + move.Player.Position.Y,
					}
					if a.Board.IsValidCoord(laser_coord) && a.Board.FieldByCoord(laser_coord).Type == "rock" {
						a.Board.FieldByCoord(laser_coord).Type = "ground"
					}
					for _, p := range a.Players {
						if p.Position.X == laser_coord.X && p.Position.Y == laser_coord.Y {
							p.Life -= 25
						}
					}
				}
			case "oil":
				if move.Player.Life > 80 {
					move.Player.Life = 100
				} else {
					move.Player.Life += 20
				}
			}
		}

		a.movePlayers(&moves)

		a.checkDeath()

		// helper coord->user map
		player_coords := make(map[coord.Coord][]*player.Player)
		for _, p := range a.Players {
			player_coords[p.Position] = append(player_coords[p.Position], p)
		}

		// activate destination traps && collect loot
		for c, players := range player_coords {
			field := a.Board.FieldByCoord(c)
			if len(players) == 1 {
				for _, l := range a.Board.FieldByCoord(c).Loot {
					players[0].Inventory[l] += 1
				}
				field.ClearLoot()
			}
			if field.Traps > 0 {
				for _, p := range players {
					p.Life -= 50 * a.Board.FieldByCoord(c).Traps
				}
				field.ClearTraps()
			}
		}

		a.checkDeath()

		// trigger inventory items
		for _, p := range a.Players {
			p.Score += p.Inventory["solar panel"]
		}
	}
}

func (a *Arena) checkDeath() {
	for _, p := range a.Players {
		if p.Life <= 0 {
			field := a.Board.FieldByCoord(p.Position)
			for loot, loot_count := range p.Inventory {
				p.Inventory[loot] = 0
				for i := 0; i < loot_count; i++ {
					field.Loot = append(field.Loot, loot)
				}
			}
			p.Life = 100
			p.Position = p.SpawnPosition
		}
	}
}

func (a *Arena) setSpawnPos() {
	board_size := uint(len(a.Board.Fields))
	spawn_coord := coord.Coord{
		uint(rand.Intn(int(board_size / 2))),
		uint(rand.Intn(int(board_size / 2))),
	}
	for !a.Board.IsValidLocation(spawn_coord) {
		if rand.Int()%2 == 0 {
			spawn_coord.X = (spawn_coord.X + 1) % board_size / 2
		} else {
			spawn_coord.Y = (spawn_coord.Y + 1) % board_size / 2
		}
	}
	for i, p := range a.Players {
		if i%2 == 0 {
			p.SpawnPosition.X = spawn_coord.X
		} else {
			p.SpawnPosition.X = board_size - spawn_coord.X - 1
		}
		if (i+1)%4 > 1 {
			p.SpawnPosition.Y = spawn_coord.Y
		} else {
			p.SpawnPosition.Y = board_size - spawn_coord.Y - 1
		}
		p.Position = p.SpawnPosition
	}

}

func (a *Arena) movePlayers(moves *[]*player.Move) {
	for _, move := range *moves {
		a.movePlayer(move)
	}
}

func (a *Arena) movePlayer(m *player.Move) {
	new_coord := m.Player.Position
	distance := uint(1)
	if m.Item == "pogo stick" {
		distance = 2
	}
	switch m.Direction {
	case "up":
		new_coord.Y -= distance
	case "down":
		new_coord.Y += distance
	case "left":
		new_coord.X -= distance
	case "right":
		new_coord.X += distance
	}
	if a.Board.IsValidLocation(new_coord) {
		m.Player.Position = new_coord
	}
}

func (a *Arena) getMoves(ch chan *player.Move) []*player.Move {
	moves := []*player.Move{}
	timeout := make(chan bool)
	go func() {
		time.Sleep(ROUND_TIMEOUT * time.Second)
		timeout <- true
	}()
	recv_break := false
	for !recv_break && len(moves) < a.getActivePlayersNum() {
		select {
		case move := <-ch:
			if move.Error != nil {
				fmt.Println("user error:", move.Error)
				break
			}
			can_move := true
			for _, m := range moves {
				if m.Player == move.Player {
					can_move = false
					break
				}
			}
			if can_move {
				moves = append(moves, move)
			} else {
				fmt.Println("Error: already moved")
			}
		case <-timeout:
			fmt.Println("Timeout")
			recv_break = true
		}
	}
	return moves
}

func (a *Arena) getActivePlayersNum() int {
	active_players := 0
	for _, player := range a.Players {
		if !player.Disconnected {
			active_players += 1
		}
	}
	return active_players
}

func (a *Arena) broadcastStatus() {
	b, err := json.Marshal(a)
	if err != nil {
		fmt.Println(err)
	}
	for _, p := range a.Players {
		p.Write(b)
	}
}

func (a *Arena) AddPlayer(conn io.ReadWriteCloser) {
	p := player.Create(player_id, conn)
	msg, _ := json.Marshal(p)
	p.Write(msg)
	player_id += 1
	a.Players = append(a.Players, p)
}
<commit_msg>[fix] reliable spawn pos algo<commit_after>package arena

import (
	"encoding/json"
	"fmt"
	"io"
	"math/rand"
	"time"

	"github.com/asciimoo/solary/arena/board"
	"github.com/asciimoo/solary/arena/coord"
	"github.com/asciimoo/solary/player"
)

const (
	ROUND_TIMEOUT = 2
	MAX_ROUNDS    = 500
)

var battle_id uint = 0
var player_id uint = 0

type Arena struct {
	Id      uint
	Round   uint
	Players []*player.Player
	Board   *board.Board
}

func Create() *Arena {
	a := &Arena{
		battle_id,
		0,
		make([]*player.Player, 0),
		board.Create(),
	}
	battle_id += 1
	return a
}

func (a *Arena) Play() {
	ch := make(chan *player.Move)
	for _, p := range a.Players {
		defer p.Conn.Close()
		go p.Read(ch)
	}
	a.setSpawnPos()
	for {
		a.Round += 1
		a.broadcastStatus()
		if a.Round == MAX_ROUNDS || a.getActivePlayersNum() == 0 {
			fmt.Println("Game", a.Id, "finished")
			return
		}
		if a.Round%100 == 0 {
			a.Board.PopulateRandomLoot()
		}
		// collect moves
		moves := a.getMoves(ch)
		// activate laser beams and traps
		for _, move := range moves {
			if move.Item == "" {
				continue
			}
			c, ok := move.Player.Inventory[move.Item]
			if !ok || c <= 0 {
				continue
			}
			move.Player.Inventory[move.Item] -= 1
			switch move.Item {
			case "trap":
				a.Board.FieldByCoord(move.Player.Position).AddTrap()
			case "laser beam":
				laser_x := 0
				laser_y := 0
				switch move.Direction {
				case "up":
					laser_y -= 1
				case "down":
					laser_y += 1
				case "left":
					laser_x -= 1
				case "right":
					laser_x += 1
				}
				for i := 1; i <= 2; i++ {
					laser_coord := coord.Coord{
						uint(laser_x*i) + move.Player.Position.X,
						uint(laser_y*i) + move.Player.Position.Y,
					}
					if a.Board.IsValidCoord(laser_coord) && a.Board.FieldByCoord(laser_coord).Type == "rock" {
						a.Board.FieldByCoord(laser_coord).Type = "ground"
					}
					for _, p := range a.Players {
						if p.Position.X == laser_coord.X && p.Position.Y == laser_coord.Y {
							p.Life -= 25
						}
					}
				}
			case "oil":
				if move.Player.Life > 80 {
					move.Player.Life = 100
				} else {
					move.Player.Life += 20
				}
			}
		}

		a.movePlayers(&moves)

		a.checkDeath()

		// helper coord->user map
		player_coords := make(map[coord.Coord][]*player.Player)
		for _, p := range a.Players {
			player_coords[p.Position] = append(player_coords[p.Position], p)
		}

		// activate destination traps && collect loot
		for c, players := range player_coords {
			field := a.Board.FieldByCoord(c)
			if len(players) == 1 {
				for _, l := range a.Board.FieldByCoord(c).Loot {
					players[0].Inventory[l] += 1
				}
				field.ClearLoot()
			}
			if field.Traps > 0 {
				for _, p := range players {
					p.Life -= 50 * a.Board.FieldByCoord(c).Traps
				}
				field.ClearTraps()
			}
		}

		a.checkDeath()

		// trigger inventory items
		for _, p := range a.Players {
			p.Score += p.Inventory["solar panel"]
		}
	}
}

func (a *Arena) checkDeath() {
	for _, p := range a.Players {
		if p.Life <= 0 {
			field := a.Board.FieldByCoord(p.Position)
			for loot, loot_count := range p.Inventory {
				p.Inventory[loot] = 0
				for i := 0; i < loot_count; i++ {
					field.Loot = append(field.Loot, loot)
				}
			}
			p.Life = 100
			p.Position = p.SpawnPosition
		}
	}
}

func (a *Arena) setSpawnPos() {
	board_size := uint(len(a.Board.Fields))
	board_half := board_size / 2
	spawn_coord := coord.Coord{
		uint(rand.Intn(int(board_half))),
		uint(rand.Intn(int(board_half))),
	}
	pos_found := false
	for x := uint(0); x <= board_half; x++ {
		for y := uint(0); y <= board_half; y++ {
			spawn_coord.X = (spawn_coord.X + x) % board_half
			spawn_coord.Y = (spawn_coord.Y + y) % board_half
			if a.Board.IsValidLocation(spawn_coord) {
				pos_found = true
				break
			}
		}
		if pos_found {
			break
		}
	}
	for i, p := range a.Players {
		if i%2 == 0 {
			p.SpawnPosition.X = spawn_coord.X
		} else {
			p.SpawnPosition.X = board_size - spawn_coord.X - 1
		}
		if (i+1)%4 > 1 {
			p.SpawnPosition.Y = spawn_coord.Y
		} else {
			p.SpawnPosition.Y = board_size - spawn_coord.Y - 1
		}
		p.Position = p.SpawnPosition
	}

}

func (a *Arena) movePlayers(moves *[]*player.Move) {
	for _, move := range *moves {
		a.movePlayer(move)
	}
}

func (a *Arena) movePlayer(m *player.Move) {
	new_coord := m.Player.Position
	distance := uint(1)
	if m.Item == "pogo stick" {
		distance = 2
	}
	switch m.Direction {
	case "up":
		new_coord.Y -= distance
	case "down":
		new_coord.Y += distance
	case "left":
		new_coord.X -= distance
	case "right":
		new_coord.X += distance
	}
	if a.Board.IsValidLocation(new_coord) {
		m.Player.Position = new_coord
	}
}

func (a *Arena) getMoves(ch chan *player.Move) []*player.Move {
	moves := []*player.Move{}
	timeout := make(chan bool)
	go func() {
		time.Sleep(ROUND_TIMEOUT * time.Second)
		timeout <- true
	}()
	recv_break := false
	for !recv_break && len(moves) < a.getActivePlayersNum() {
		select {
		case move := <-ch:
			if move.Error != nil {
				fmt.Println("user error:", move.Error)
				break
			}
			can_move := true
			for _, m := range moves {
				if m.Player == move.Player {
					can_move = false
					break
				}
			}
			if can_move {
				moves = append(moves, move)
			} else {
				fmt.Println("Error: already moved")
			}
		case <-timeout:
			fmt.Println("Timeout")
			recv_break = true
		}
	}
	return moves
}

func (a *Arena) getActivePlayersNum() int {
	active_players := 0
	for _, player := range a.Players {
		if !player.Disconnected {
			active_players += 1
		}
	}
	return active_players
}

func (a *Arena) broadcastStatus() {
	b, err := json.Marshal(a)
	if err != nil {
		fmt.Println(err)
	}
	for _, p := range a.Players {
		p.Write(b)
	}
}

func (a *Arena) AddPlayer(conn io.ReadWriteCloser) {
	p := player.Create(player_id, conn)
	msg, _ := json.Marshal(p)
	p.Write(msg)
	player_id += 1
	a.Players = append(a.Players, p)
}
<|endoftext|>


<commit_before>package asset

import (
	"context"
	"encoding/binary"
	"encoding/json"
	"strings"
	"sync"

	"github.com/golang/groupcache/lru"
	dbm "github.com/tendermint/tmlibs/db"
	"golang.org/x/crypto/sha3"

	"github.com/bytom/blockchain/signers"
	"github.com/bytom/consensus"
	"github.com/bytom/crypto/ed25519"
	"github.com/bytom/crypto/ed25519/chainkd"
	chainjson "github.com/bytom/encoding/json"
	"github.com/bytom/errors"
	"github.com/bytom/protocol"
	"github.com/bytom/protocol/bc"
	"github.com/bytom/protocol/vm/vmutil"
)

// DefaultNativeAsset is the native BTM asset
var DefaultNativeAsset *Asset

const (
	maxAssetCache = 1000
	assetPrefix   = "ASS:"
	//AliasPrefix is asset alias prefix
	AliasPrefix = "ALS:"
	//ExternalAssetPrefix is external definition assets prefix
	ExternalAssetPrefix = "EXA"
	indexPrefix         = "ASSIDX:"
)

func initNativeAsset() {
	signer := &signers.Signer{Type: "internal"}
	alias := consensus.BTMAlias

	definitionBytes, _ := serializeAssetDef(consensus.BTMDefinitionMap)
	DefaultNativeAsset = &Asset{
		Signer:            signer,
		AssetID:           *consensus.BTMAssetID,
		Alias:             &alias,
		VMVersion:         1,
		DefinitionMap:     consensus.BTMDefinitionMap,
		RawDefinitionByte: definitionBytes,
	}
}

// AliasKey is alias of asset
func AliasKey(name string) []byte {
	return []byte(AliasPrefix + name)
}

//Key asset store prefix
func Key(id *bc.AssetID) []byte {
	name := id.String()
	return []byte(assetPrefix + name)
}

func indexKey(xpub chainkd.XPub) []byte {
	return []byte(indexPrefix + xpub.String())
}

//CalcExtAssetKey return store external assets key
func CalcExtAssetKey(id *bc.AssetID) []byte {
	name := id.String()
	return []byte(ExternalAssetPrefix + name)
}

// pre-define errors for supporting bytom errorFormatter
var (
	ErrDuplicateAlias = errors.New("duplicate asset alias")
	ErrDuplicateAsset = errors.New("duplicate asset id")
	ErrSerializing    = errors.New("serializing asset definition")
	ErrMarshalAsset   = errors.New("failed marshal asset")
	ErrFindAsset      = errors.New("fail to find asset")
	ErrInternalAsset  = errors.New("btm has been defined as the internal asset")
	ErrNullAlias      = errors.New("null asset alias")
)

//NewRegistry create new registry
func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
	initNativeAsset()
	return &Registry{
		db:         db,
		chain:      chain,
		cache:      lru.New(maxAssetCache),
		aliasCache: lru.New(maxAssetCache),
	}
}

// Registry tracks and stores all known assets on a blockchain.
type Registry struct {
	db    dbm.DB
	chain *protocol.Chain

	cacheMu    sync.Mutex
	cache      *lru.Cache
	aliasCache *lru.Cache

	assetIndexMu sync.Mutex
}

//Asset describe asset on bytom chain
type Asset struct {
	*signers.Signer
	AssetID           bc.AssetID             `json:"id"`
	Alias             *string                `json:"alias"`
	VMVersion         uint64                 `json:"vm_version"`
	IssuanceProgram   chainjson.HexBytes     `json:"issue_program"`
	Tags              map[string]interface{} `json:"tags"`
	RawDefinitionByte chainjson.HexBytes     `json:"raw_definition_byte"`
	DefinitionMap     map[string]interface{} `json:"definition"`
}

func (reg *Registry) getNextAssetIndex(xpubs []chainkd.XPub) (*uint64, error) {
	reg.assetIndexMu.Lock()
	defer reg.assetIndexMu.Unlock()

	var nextIndex uint64 = 1

	if rawIndex := reg.db.Get(indexKey(xpubs[0])); rawIndex != nil {
		nextIndex = binary.LittleEndian.Uint64(rawIndex) + 1
	}

	buf := make([]byte, 8)
	binary.LittleEndian.PutUint64(buf, nextIndex)
	reg.db.Set(indexKey(xpubs[0]), buf)

	return &nextIndex, nil
}

// Define defines a new Asset.
func (reg *Registry) Define(xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, alias string, tags map[string]interface{}) (*Asset, error) {
	if len(xpubs) == 0 {
		return nil, errors.Wrap(signers.ErrNoXPubs)
	}

	normalizedAlias := strings.ToUpper(strings.TrimSpace(alias))
	if normalizedAlias == consensus.BTMAlias {
		return nil, ErrInternalAsset
	}

	if existed := reg.db.Get(AliasKey(normalizedAlias)); existed != nil {
		return nil, ErrDuplicateAlias
	}

	nextAssetIndex, err := reg.getNextAssetIndex(xpubs)
	if err != nil {
		return nil, errors.Wrap(err, "get asset index error")
	}

	assetSigner, err := signers.Create("asset", xpubs, quorum, *nextAssetIndex)
	if err != nil {
		return nil, err
	}

	rawDefinition, err := serializeAssetDef(definition)
	if err != nil {
		return nil, ErrSerializing
	}

	path := signers.Path(assetSigner, signers.AssetKeySpace)
	derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
	derivedPKs := chainkd.XPubKeys(derivedXPubs)
	issuanceProgram, vmver, err := multisigIssuanceProgram(derivedPKs, assetSigner.Quorum)
	if err != nil {
		return nil, err
	}

	defHash := bc.NewHash(sha3.Sum256(rawDefinition))
	asset := &Asset{
		DefinitionMap:     definition,
		RawDefinitionByte: rawDefinition,
		VMVersion:         vmver,
		IssuanceProgram:   issuanceProgram,
		AssetID:           bc.ComputeAssetID(issuanceProgram, vmver, &defHash),
		Signer:            assetSigner,
		Tags:              tags,
	}

	if existAsset := reg.db.Get(Key(&asset.AssetID)); existAsset != nil {
		return nil, ErrDuplicateAsset
	}

	if alias != "" {
		asset.Alias = &normalizedAlias
	}

	ass, err := json.Marshal(asset)
	if err != nil {
		return nil, ErrMarshalAsset
	}

	storeBatch := reg.db.NewBatch()
	storeBatch.Set(AliasKey(normalizedAlias), []byte(asset.AssetID.String()))
	storeBatch.Set(Key(&asset.AssetID), ass)
	storeBatch.Write()

	return asset, nil
}

// UpdateTags modifies the tags of the specified asset. The asset may be
// identified either by id or alias, but not both.
func (reg *Registry) UpdateTags(ctx context.Context, assetInfo string, tags map[string]interface{}) (err error) {
	asset := &Asset{}
	if asset, err = reg.FindByAlias(ctx, assetInfo); err != nil {
		assetID := &bc.AssetID{}
		if err := assetID.UnmarshalText([]byte(assetInfo)); err != nil {
			return err
		}
		if asset, err = reg.FindByID(ctx, assetID); err != nil {
			return err
		}
	}

	asset.Tags = tags
	rawAsset, err := json.Marshal(asset)
	if err != nil {
		return ErrMarshalAsset
	}

	reg.db.Set(Key(&asset.AssetID), rawAsset)
	reg.cacheMu.Lock()
	reg.cache.Add(asset.AssetID, asset)
	reg.cacheMu.Unlock()
	return nil
}

// FindByID retrieves an Asset record along with its signer, given an assetID.
func (reg *Registry) FindByID(ctx context.Context, id *bc.AssetID) (*Asset, error) {
	reg.cacheMu.Lock()
	cached, ok := reg.cache.Get(id.String())
	reg.cacheMu.Unlock()
	if ok {
		return cached.(*Asset), nil
	}

	bytes := reg.db.Get(Key(id))
	if bytes == nil {
		return nil, ErrFindAsset
	}

	asset := &Asset{}
	if err := json.Unmarshal(bytes, asset); err != nil {
		return nil, err
	}

	reg.cacheMu.Lock()
	reg.cache.Add(id.String(), asset)
	reg.cacheMu.Unlock()
	return asset, nil
}

//GetIDByAlias return AssetID string and nil by asset alias,if err ,return "" and err
func (reg *Registry) GetIDByAlias(alias string) (string, error) {
	rawID := reg.db.Get(AliasKey(alias))
	if rawID == nil {
		return "", ErrFindAsset
	}
	return string(rawID), nil
}

// FindByAlias retrieves an Asset record along with its signer,
// given an asset alias.
func (reg *Registry) FindByAlias(ctx context.Context, alias string) (*Asset, error) {
	reg.cacheMu.Lock()
	cachedID, ok := reg.aliasCache.Get(alias)
	reg.cacheMu.Unlock()
	if ok {
		return reg.FindByID(ctx, cachedID.(*bc.AssetID))
	}

	rawID := reg.db.Get(AliasKey(alias))
	if rawID == nil {
		return nil, errors.Wrapf(ErrFindAsset, "no such asset, alias: %s", alias)
	}

	assetID := &bc.AssetID{}
	if err := assetID.UnmarshalText(rawID); err != nil {
		return nil, err
	}

	reg.cacheMu.Lock()
	reg.aliasCache.Add(alias, assetID)
	reg.cacheMu.Unlock()
	return reg.FindByID(ctx, assetID)
}

//GetAliasByID return asset alias string by AssetID string
func (reg *Registry) GetAliasByID(id string) string {
	//btm
	if id == consensus.BTMAssetID.String() {
		return consensus.BTMAlias
	}

	assetID := &bc.AssetID{}
	if err := assetID.UnmarshalText([]byte(id)); err != nil {
		return ""
	}

	asset, err := reg.FindByID(nil, assetID)
	if err != nil {
		return ""
	}

	return *asset.Alias
}

// ListAssets returns the accounts in the db
func (reg *Registry) ListAssets(id string) ([]*Asset, error) {
	assets := []*Asset{DefaultNativeAsset}
	assetIter := reg.db.IteratorPrefix([]byte(assetPrefix + id))
	defer assetIter.Release()

	for assetIter.Next() {
		asset := &Asset{}
		if err := json.Unmarshal(assetIter.Value(), asset); err != nil {
			return nil, err
		}
		assets = append(assets, asset)
	}
	return assets, nil
}

// serializeAssetDef produces a canonical byte representation of an asset
// definition. Currently, this is implemented using pretty-printed JSON.
// As is the standard for Go's map[string] serialization, object keys will
// appear in lexicographic order. Although this is mostly meant for machine
// consumption, the JSON is pretty-printed for easy reading.
// The empty asset def is an empty byte slice.
func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
	if def == nil {
		def = make(map[string]interface{}, 0)
	}
	return json.MarshalIndent(def, "", "  ")
}

func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (program []byte, vmversion uint64, err error) {
	issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired)
	if err != nil {
		return nil, 0, err
	}
	builder := vmutil.NewBuilder()
	builder.AddRawBytes(issuanceProg)
	prog, err := builder.Build()
	return prog, 1, err
}

//UpdateAssetAlias updates oldAlias to newAlias
func (reg *Registry) UpdateAssetAlias(oldAlias, newAlias string) error {
	if oldAlias == consensus.BTMAlias || newAlias == consensus.BTMAlias {
		return ErrInternalAsset
	}

	if oldAlias == "" || newAlias == "" {
		return ErrNullAlias
	}

	if _, err := reg.GetIDByAlias(newAlias); err == nil {
		return ErrDuplicateAlias
	}

	findAsset, err := reg.FindByAlias(nil, oldAlias)
	if err != nil {
		return err
	}

	storeBatch := reg.db.NewBatch()
	findAsset.Alias = &newAlias
	assetID := &findAsset.AssetID
	rawAsset, err := json.Marshal(findAsset)
	if err != nil {
		return err
	}

	storeBatch.Set(Key(assetID), rawAsset)
	storeBatch.Set(AliasKey(newAlias), []byte(assetID.String()))
	storeBatch.Delete(AliasKey(oldAlias))
	storeBatch.Write()

	reg.cacheMu.Lock()
	reg.aliasCache.Add(newAlias, assetID)
	reg.aliasCache.Remove(oldAlias)
	reg.cacheMu.Unlock()

	return nil
}
<commit_msg>modify func instruction<commit_after>package asset

import (
	"context"
	"encoding/binary"
	"encoding/json"
	"strings"
	"sync"

	"github.com/golang/groupcache/lru"
	dbm "github.com/tendermint/tmlibs/db"
	"golang.org/x/crypto/sha3"

	"github.com/bytom/blockchain/signers"
	"github.com/bytom/consensus"
	"github.com/bytom/crypto/ed25519"
	"github.com/bytom/crypto/ed25519/chainkd"
	chainjson "github.com/bytom/encoding/json"
	"github.com/bytom/errors"
	"github.com/bytom/protocol"
	"github.com/bytom/protocol/bc"
	"github.com/bytom/protocol/vm/vmutil"
)

// DefaultNativeAsset native BTM asset
var DefaultNativeAsset *Asset

const (
	maxAssetCache = 1000
	assetPrefix   = "ASS:"
	//AliasPrefix is asset alias prefix
	AliasPrefix = "ALS:"
	//ExternalAssetPrefix is external definition assets prefix
	ExternalAssetPrefix = "EXA"
	indexPrefix         = "ASSIDX:"
)

func initNativeAsset() {
	signer := &signers.Signer{Type: "internal"}
	alias := consensus.BTMAlias

	definitionBytes, _ := serializeAssetDef(consensus.BTMDefinitionMap)
	DefaultNativeAsset = &Asset{
		Signer:            signer,
		AssetID:           *consensus.BTMAssetID,
		Alias:             &alias,
		VMVersion:         1,
		DefinitionMap:     consensus.BTMDefinitionMap,
		RawDefinitionByte: definitionBytes,
	}
}

// AliasKey store asset alias prefix
func AliasKey(name string) []byte {
	return []byte(AliasPrefix + name)
}

//Key asset store prefix
func Key(id *bc.AssetID) []byte {
	name := id.String()
	return []byte(assetPrefix + name)
}

func indexKey(xpub chainkd.XPub) []byte {
	return []byte(indexPrefix + xpub.String())
}

//CalcExtAssetKey return store external assets key
func CalcExtAssetKey(id *bc.AssetID) []byte {
	name := id.String()
	return []byte(ExternalAssetPrefix + name)
}

// pre-define errors for supporting bytom errorFormatter
var (
	ErrDuplicateAlias = errors.New("duplicate asset alias")
	ErrDuplicateAsset = errors.New("duplicate asset id")
	ErrSerializing    = errors.New("serializing asset definition")
	ErrMarshalAsset   = errors.New("failed marshal asset")
	ErrFindAsset      = errors.New("fail to find asset")
	ErrInternalAsset  = errors.New("btm has been defined as the internal asset")
	ErrNullAlias      = errors.New("null asset alias")
)

//NewRegistry create new registry
func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
	initNativeAsset()
	return &Registry{
		db:         db,
		chain:      chain,
		cache:      lru.New(maxAssetCache),
		aliasCache: lru.New(maxAssetCache),
	}
}

// Registry tracks and stores all known assets on a blockchain.
type Registry struct {
	db    dbm.DB
	chain *protocol.Chain

	cacheMu    sync.Mutex
	cache      *lru.Cache
	aliasCache *lru.Cache

	assetIndexMu sync.Mutex
}

//Asset describe asset on bytom chain
type Asset struct {
	*signers.Signer
	AssetID           bc.AssetID             `json:"id"`
	Alias             *string                `json:"alias"`
	VMVersion         uint64                 `json:"vm_version"`
	IssuanceProgram   chainjson.HexBytes     `json:"issue_program"`
	Tags              map[string]interface{} `json:"tags"`
	RawDefinitionByte chainjson.HexBytes     `json:"raw_definition_byte"`
	DefinitionMap     map[string]interface{} `json:"definition"`
}

func (reg *Registry) getNextAssetIndex(xpubs []chainkd.XPub) (*uint64, error) {
	reg.assetIndexMu.Lock()
	defer reg.assetIndexMu.Unlock()

	var nextIndex uint64 = 1

	if rawIndex := reg.db.Get(indexKey(xpubs[0])); rawIndex != nil {
		nextIndex = binary.LittleEndian.Uint64(rawIndex) + 1
	}

	buf := make([]byte, 8)
	binary.LittleEndian.PutUint64(buf, nextIndex)
	reg.db.Set(indexKey(xpubs[0]), buf)

	return &nextIndex, nil
}

// Define defines a new Asset.
func (reg *Registry) Define(xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, alias string, tags map[string]interface{}) (*Asset, error) {
	if len(xpubs) == 0 {
		return nil, errors.Wrap(signers.ErrNoXPubs)
	}

	normalizedAlias := strings.ToUpper(strings.TrimSpace(alias))
	if normalizedAlias == consensus.BTMAlias {
		return nil, ErrInternalAsset
	}

	if existed := reg.db.Get(AliasKey(normalizedAlias)); existed != nil {
		return nil, ErrDuplicateAlias
	}

	nextAssetIndex, err := reg.getNextAssetIndex(xpubs)
	if err != nil {
		return nil, errors.Wrap(err, "get asset index error")
	}

	assetSigner, err := signers.Create("asset", xpubs, quorum, *nextAssetIndex)
	if err != nil {
		return nil, err
	}

	rawDefinition, err := serializeAssetDef(definition)
	if err != nil {
		return nil, ErrSerializing
	}

	path := signers.Path(assetSigner, signers.AssetKeySpace)
	derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
	derivedPKs := chainkd.XPubKeys(derivedXPubs)
	issuanceProgram, vmver, err := multisigIssuanceProgram(derivedPKs, assetSigner.Quorum)
	if err != nil {
		return nil, err
	}

	defHash := bc.NewHash(sha3.Sum256(rawDefinition))
	asset := &Asset{
		DefinitionMap:     definition,
		RawDefinitionByte: rawDefinition,
		VMVersion:         vmver,
		IssuanceProgram:   issuanceProgram,
		AssetID:           bc.ComputeAssetID(issuanceProgram, vmver, &defHash),
		Signer:            assetSigner,
		Tags:              tags,
	}

	if existAsset := reg.db.Get(Key(&asset.AssetID)); existAsset != nil {
		return nil, ErrDuplicateAsset
	}

	if alias != "" {
		asset.Alias = &normalizedAlias
	}

	ass, err := json.Marshal(asset)
	if err != nil {
		return nil, ErrMarshalAsset
	}

	storeBatch := reg.db.NewBatch()
	storeBatch.Set(AliasKey(normalizedAlias), []byte(asset.AssetID.String()))
	storeBatch.Set(Key(&asset.AssetID), ass)
	storeBatch.Write()

	return asset, nil
}

// UpdateTags modifies the tags of the specified asset. The asset may be
// identified either by id or alias, but not both.
func (reg *Registry) UpdateTags(ctx context.Context, assetInfo string, tags map[string]interface{}) (err error) {
	asset := &Asset{}
	if asset, err = reg.FindByAlias(ctx, assetInfo); err != nil {
		assetID := &bc.AssetID{}
		if err := assetID.UnmarshalText([]byte(assetInfo)); err != nil {
			return err
		}
		if asset, err = reg.FindByID(ctx, assetID); err != nil {
			return err
		}
	}

	asset.Tags = tags
	rawAsset, err := json.Marshal(asset)
	if err != nil {
		return ErrMarshalAsset
	}

	reg.db.Set(Key(&asset.AssetID), rawAsset)
	reg.cacheMu.Lock()
	reg.cache.Add(asset.AssetID, asset)
	reg.cacheMu.Unlock()
	return nil
}

// FindByID retrieves an Asset record along with its signer, given an assetID.
func (reg *Registry) FindByID(ctx context.Context, id *bc.AssetID) (*Asset, error) {
	reg.cacheMu.Lock()
	cached, ok := reg.cache.Get(id.String())
	reg.cacheMu.Unlock()
	if ok {
		return cached.(*Asset), nil
	}

	bytes := reg.db.Get(Key(id))
	if bytes == nil {
		return nil, ErrFindAsset
	}

	asset := &Asset{}
	if err := json.Unmarshal(bytes, asset); err != nil {
		return nil, err
	}

	reg.cacheMu.Lock()
	reg.cache.Add(id.String(), asset)
	reg.cacheMu.Unlock()
	return asset, nil
}

//GetIDByAlias return AssetID string and nil by asset alias,if err ,return "" and err
func (reg *Registry) GetIDByAlias(alias string) (string, error) {
	rawID := reg.db.Get(AliasKey(alias))
	if rawID == nil {
		return "", ErrFindAsset
	}
	return string(rawID), nil
}

// FindByAlias retrieves an Asset record along with its signer,
// given an asset alias.
func (reg *Registry) FindByAlias(ctx context.Context, alias string) (*Asset, error) {
	reg.cacheMu.Lock()
	cachedID, ok := reg.aliasCache.Get(alias)
	reg.cacheMu.Unlock()
	if ok {
		return reg.FindByID(ctx, cachedID.(*bc.AssetID))
	}

	rawID := reg.db.Get(AliasKey(alias))
	if rawID == nil {
		return nil, errors.Wrapf(ErrFindAsset, "no such asset, alias: %s", alias)
	}

	assetID := &bc.AssetID{}
	if err := assetID.UnmarshalText(rawID); err != nil {
		return nil, err
	}

	reg.cacheMu.Lock()
	reg.aliasCache.Add(alias, assetID)
	reg.cacheMu.Unlock()
	return reg.FindByID(ctx, assetID)
}

//GetAliasByID return asset alias string by AssetID string
func (reg *Registry) GetAliasByID(id string) string {
	//btm
	if id == consensus.BTMAssetID.String() {
		return consensus.BTMAlias
	}

	assetID := &bc.AssetID{}
	if err := assetID.UnmarshalText([]byte(id)); err != nil {
		return ""
	}

	asset, err := reg.FindByID(nil, assetID)
	if err != nil {
		return ""
	}

	return *asset.Alias
}

// ListAssets returns the accounts in the db
func (reg *Registry) ListAssets(id string) ([]*Asset, error) {
	assets := []*Asset{DefaultNativeAsset}
	assetIter := reg.db.IteratorPrefix([]byte(assetPrefix + id))
	defer assetIter.Release()

	for assetIter.Next() {
		asset := &Asset{}
		if err := json.Unmarshal(assetIter.Value(), asset); err != nil {
			return nil, err
		}
		assets = append(assets, asset)
	}
	return assets, nil
}

// serializeAssetDef produces a canonical byte representation of an asset
// definition. Currently, this is implemented using pretty-printed JSON.
// As is the standard for Go's map[string] serialization, object keys will
// appear in lexicographic order. Although this is mostly meant for machine
// consumption, the JSON is pretty-printed for easy reading.
func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
	if def == nil {
		def = make(map[string]interface{}, 0)
	}
	return json.MarshalIndent(def, "", "  ")
}

func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (program []byte, vmversion uint64, err error) {
	issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired)
	if err != nil {
		return nil, 0, err
	}
	builder := vmutil.NewBuilder()
	builder.AddRawBytes(issuanceProg)
	prog, err := builder.Build()
	return prog, 1, err
}

//UpdateAssetAlias updates oldAlias to newAlias
func (reg *Registry) UpdateAssetAlias(oldAlias, newAlias string) error {
	if oldAlias == consensus.BTMAlias || newAlias == consensus.BTMAlias {
		return ErrInternalAsset
	}

	if oldAlias == "" || newAlias == "" {
		return ErrNullAlias
	}

	if _, err := reg.GetIDByAlias(newAlias); err == nil {
		return ErrDuplicateAlias
	}

	findAsset, err := reg.FindByAlias(nil, oldAlias)
	if err != nil {
		return err
	}

	storeBatch := reg.db.NewBatch()
	findAsset.Alias = &newAlias
	assetID := &findAsset.AssetID
	rawAsset, err := json.Marshal(findAsset)
	if err != nil {
		return err
	}

	storeBatch.Set(Key(assetID), rawAsset)
	storeBatch.Set(AliasKey(newAlias), []byte(assetID.String()))
	storeBatch.Delete(AliasKey(oldAlias))
	storeBatch.Write()

	reg.cacheMu.Lock()
	reg.aliasCache.Add(newAlias, assetID)
	reg.aliasCache.Remove(oldAlias)
	reg.cacheMu.Unlock()

	return nil
}
<|endoftext|>


<commit_before>package audio

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
)

var workingDir string = path.Join(os.TempDir(), "apollo-audio")
var clipDir string = path.Join(workingDir, "clips")
var AAC string = "aac"
var M4A string = "m4a"
var OGG string = "ogg"

/* container for the request variables */
type RequestVars struct {
	Mission  int
	Channels []string
	Format   string
	Start    int
	Duration int
}

/* local paths to audio files for all channels belonging to particular time slice */
type TimeSlice struct {
	start  int
	end    int
	chunks map[int]AudioChunk
}

func NewTimeSlice(start, end int) TimeSlice {
	chunks := make(map[int]AudioChunk)
	return TimeSlice{start, end, chunks}
}

type AudioChunk struct {
	start     int
	end       int
	url       string
	localPath string
	channel   int
}

func InitDirs() {
	makeDir(workingDir)
	makeDir(clipDir)
}

func GetRequestSlices(rv RequestVars) []TimeSlice {
	var slices []TimeSlice
	// Get db
	db := connectDb()
	defer db.Close()

	// Psql array for ANY query
	channelString := fmt.Sprintf("{%s}", strings.Join(rv.Channels, ","))
	reqEnd := rv.Start + rv.Duration

	// Query
	stmt, err := db.Prepare("SELECT met_start, met_end, url, channel FROM channel_chunks WHERE channel = ANY($1::integer[]) AND met_end > $2 AND met_start < $3 ORDER BY met_start")
	check(err)
	rows, err := stmt.Query(channelString, rv.Start, reqEnd)
	check(err)
	defer rows.Close()

	//Scan results and build slices
	lastStart := -1
	for rows.Next() {
		var chunk AudioChunk
		err = rows.Scan(&chunk.start, &chunk.end, &chunk.url, &chunk.channel)
		if err != nil {
			log.Println("Error reading row", err)
			continue
		}
		//Check for new timeslice
		if chunk.start > lastStart {
			slices = append(slices, NewTimeSlice(chunk.start, chunk.end))
			lastStart = chunk.start
		}
		// Set local name
		loc := fmt.Sprintf("mission_%d_channel_%d_%d.wav", rv.Mission, chunk.channel, chunk.start)
		chunk.localPath = path.Join(clipDir, loc)

		// Add to proper slice
		for i, a := range slices {
			if chunk.start == a.start && chunk.end == a.end {
				slices[i].chunks[chunk.channel] = chunk
				break
			}
		}
	}
	return slices
}

func getSoxTrimArgs(i int, rv RequestVars, slices []TimeSlice) (args []string) {
	slice := slices[i]
	trimOffset := 0
	if i == 0 && rv.Start > slice.start {
		trimOffset = rv.Start - slice.start
		offset := float64(trimOffset) / 1000.0
		offStr := strconv.FormatFloat(offset, 'f', 4, 64)
		log.Println("Trimming first slice by", offStr)
		args = append(args, "trim", offStr)
	}
	reqEnd := rv.Start + rv.Duration
	if i == len(slices)-1 && slice.end > reqEnd {
		var duration int
		if rv.Start > slice.start {
			duration = reqEnd - rv.Start
		} else {
			duration = reqEnd - slice.start
		}
		df := float64(duration) / 1000.0
		durStr := strconv.FormatFloat(df, 'f', 4, 64)
		log.Println("Trimming last slice by", durStr)

		// Need starting offset if not already set
		if trimOffset == 0 {
			args = append(args, "trim", "0", durStr)
		} else {
			args = append(args, durStr)
		}
	}
	return args
}

func DownloadAndStream(slices []TimeSlice, rv RequestVars, w io.Writer) {
	// Download all here. Could be done concurrently while prev slice is streaming
	DownloadAllAudio(slices)

	// Build cmds
	sox, err := exec.LookPath("sox")
	check(err)
	log.Println("using sox " + sox)
	ffmpeg, err := exec.LookPath("ffmpeg")
	check(err)
	log.Println("using ffmpeg " + ffmpeg)

	// Process and stream each chunk
	for i, slice := range slices {
		var chunkPaths []string
		// Gather paths
		for _, ch := range slice.chunks {
			chunkPaths = append(chunkPaths, ch.localPath)
		}
		// Merge the channels
		soxArgs := []string{"-t", "wav"}
		// Only merge if there are multiple files
		if len(chunkPaths) > 1 {
			soxArgs = append(soxArgs, "-m")
		}
		soxArgs = append(soxArgs, chunkPaths...)
		soxArgs = append(soxArgs, "-p")

		// Handle trim cases on start and end
		soxArgs = append(soxArgs, getSoxTrimArgs(i, rv, slices)...)

		log.Println("running sox", strings.Join(soxArgs, " "))
		soxCommand := exec.Command(sox, soxArgs...)

		// Transcode the result
		var ffmpegArgs []string
		if rv.Format == AAC || rv.Format == M4A {
			ffmpegArgs = []string{"-i", "-", "-c:a", "libfdk_aac", "-b:a", "256k", "-f", M4A, "pipe:"}
			// works, but gotta compile ffmpeg on server with special options
		} else if rv.Format == OGG {
			ffmpegArgs = []string{"-i", "-", "-c:a", "libvorbis", "-qscale:a", "6", "-f", OGG, "pipe:"}
		} else {
			log.Println("unsupported output format requested. break some rools.")
			ffmpegArgs = []string{"-i", "-", "-f", "mp3", "-ab", "256k", "pipe:"}
		}
		log.Println("running ffmpeg", strings.Join(ffmpegArgs, " "))
		ffmpegCommand := exec.Command(ffmpeg, ffmpegArgs...)

		ffmpegCommand.Stdin, _ = soxCommand.StdoutPipe()
		ffmpegCommand.Stdout = w
		ffmpegCommand.Stderr = os.Stdout

		ffmpegCommand.Start()
		soxCommand.Run()
		ffmpegCommand.Wait()
	}
}

func DownloadAndEncode(slices []TimeSlice, rv RequestVars) string {
	// Download all here. Could be done concurrently while prev slice is streaming
	DownloadAllAudio(slices)

	// Build cmds
	soxPath, err := exec.LookPath("sox")
	check(err)
	log.Println("using sox " + soxPath)
	ffmpegPath, err := exec.LookPath("ffmpeg")
	check(err)
	log.Println("using ffmpeg " + ffmpegPath)

	audioName := fmt.Sprintf("channels_%s_%d_%d.m4a", strings.Join(rv.Channels, "."), rv.Start, rv.Duration)
	outFile := path.Join(clipDir, audioName)

	// Merge and concat all files
	// sox "|sox -m in1 in2 -p" "|sox -m in3 in4 -p" -p
	// Use shell for sox to handle funky args
	soxArgs := []string{"sox"}
	for _, slice := range slices {
		var chunkPaths []string
		var cmdStr string
		// Get files for this slice
		for _, ch := range slice.chunks {
			chunkPaths = append(chunkPaths, ch.localPath)
		}
		// Only merge with multiple files
		if len(chunkPaths) == 1 {
			cmdStr = chunkPaths[0]
		} else {
			cmdStr = fmt.Sprintf("\"| %s -m %s -p\"", "sox", strings.Join(chunkPaths, " "))
		}
		soxArgs = append(soxArgs, cmdStr) //strings.Fields(cmdStr)...
	}
	soxArgs = append(soxArgs, "-p")

	ffmpegArgs := []string{"-i", "-", "-strict", "-2", "-c:a", "aac", "-b:a", "128k", "-f", "mp4", outFile}
	ffmpegArgs = append(ffmpegArgs, "-y") // Force ovewrite

	soxCmd := strings.Join(soxArgs, " ")
	log.Println("running sox", soxCmd)
	soxCommand := exec.Command("sh", "-c", soxCmd)

	log.Println("running ffmpeg", strings.Join(ffmpegArgs, " "))
	ffmpegCommand := exec.Command(ffmpegPath, ffmpegArgs...)

	ffmpegCommand.Stdin, err = soxCommand.StdoutPipe()
	if err != nil {
		log.Fatal("Cant connect sox pipe")
	}
	ffmpegCommand.Stdout = os.Stdout
	ffmpegCommand.Stderr = os.Stdout
	soxCommand.Stderr = os.Stdout

	ffmpegCommand.Start()
	soxCommand.Run()
	ffmpegCommand.Wait()

	log.Println("Saved output to", outFile)
	return outFile
}
<commit_msg>lower br<commit_after>package audio

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
)

var workingDir string = path.Join(os.TempDir(), "apollo-audio")
var clipDir string = path.Join(workingDir, "clips")
var AAC string = "aac"
var M4A string = "m4a"
var OGG string = "ogg"

/* container for the request variables */
type RequestVars struct {
	Mission  int
	Channels []string
	Format   string
	Start    int
	Duration int
}

/* local paths to audio files for all channels belonging to particular time slice */
type TimeSlice struct {
	start  int
	end    int
	chunks map[int]AudioChunk
}

func NewTimeSlice(start, end int) TimeSlice {
	chunks := make(map[int]AudioChunk)
	return TimeSlice{start, end, chunks}
}

type AudioChunk struct {
	start     int
	end       int
	url       string
	localPath string
	channel   int
}

func InitDirs() {
	makeDir(workingDir)
	makeDir(clipDir)
}

func GetRequestSlices(rv RequestVars) []TimeSlice {
	var slices []TimeSlice
	// Get db
	db := connectDb()
	defer db.Close()

	// Psql array for ANY query
	channelString := fmt.Sprintf("{%s}", strings.Join(rv.Channels, ","))
	reqEnd := rv.Start + rv.Duration

	// Query
	stmt, err := db.Prepare("SELECT met_start, met_end, url, channel FROM channel_chunks WHERE channel = ANY($1::integer[]) AND met_end > $2 AND met_start < $3 ORDER BY met_start")
	check(err)
	rows, err := stmt.Query(channelString, rv.Start, reqEnd)
	check(err)
	defer rows.Close()

	//Scan results and build slices
	lastStart := -1
	for rows.Next() {
		var chunk AudioChunk
		err = rows.Scan(&chunk.start, &chunk.end, &chunk.url, &chunk.channel)
		if err != nil {
			log.Println("Error reading row", err)
			continue
		}
		//Check for new timeslice
		if chunk.start > lastStart {
			slices = append(slices, NewTimeSlice(chunk.start, chunk.end))
			lastStart = chunk.start
		}
		// Set local name
		loc := fmt.Sprintf("mission_%d_channel_%d_%d.wav", rv.Mission, chunk.channel, chunk.start)
		chunk.localPath = path.Join(clipDir, loc)

		// Add to proper slice
		for i, a := range slices {
			if chunk.start == a.start && chunk.end == a.end {
				slices[i].chunks[chunk.channel] = chunk
				break
			}
		}
	}
	return slices
}

func getSoxTrimArgs(i int, rv RequestVars, slices []TimeSlice) (args []string) {
	slice := slices[i]
	trimOffset := 0
	if i == 0 && rv.Start > slice.start {
		trimOffset = rv.Start - slice.start
		offset := float64(trimOffset) / 1000.0
		offStr := strconv.FormatFloat(offset, 'f', 4, 64)
		log.Println("Trimming first slice by", offStr)
		args = append(args, "trim", offStr)
	}
	reqEnd := rv.Start + rv.Duration
	if i == len(slices)-1 && slice.end > reqEnd {
		var duration int
		if rv.Start > slice.start {
			duration = reqEnd - rv.Start
		} else {
			duration = reqEnd - slice.start
		}
		df := float64(duration) / 1000.0
		durStr := strconv.FormatFloat(df, 'f', 4, 64)
		log.Println("Trimming last slice by", durStr)

		// Need starting offset if not already set
		if trimOffset == 0 {
			args = append(args, "trim", "0", durStr)
		} else {
			args = append(args, durStr)
		}
	}
	return args
}

func DownloadAndStream(slices []TimeSlice, rv RequestVars, w io.Writer) {
	// Download all here. Could be done concurrently while prev slice is streaming
	DownloadAllAudio(slices)

	// Build cmds
	sox, err := exec.LookPath("sox")
	check(err)
	log.Println("using sox " + sox)
	ffmpeg, err := exec.LookPath("ffmpeg")
	check(err)
	log.Println("using ffmpeg " + ffmpeg)

	// Process and stream each chunk
	for i, slice := range slices {
		var chunkPaths []string
		// Gather paths
		for _, ch := range slice.chunks {
			chunkPaths = append(chunkPaths, ch.localPath)
		}
		// Merge the channels
		soxArgs := []string{"-t", "wav"}
		// Only merge if there are multiple files
		if len(chunkPaths) > 1 {
			soxArgs = append(soxArgs, "-m")
		}
		soxArgs = append(soxArgs, chunkPaths...)
		soxArgs = append(soxArgs, "-p")

		// Handle trim cases on start and end
		soxArgs = append(soxArgs, getSoxTrimArgs(i, rv, slices)...)

		log.Println("running sox", strings.Join(soxArgs, " "))
		soxCommand := exec.Command(sox, soxArgs...)

		// Transcode the result
		var ffmpegArgs []string
		if rv.Format == AAC || rv.Format == M4A {
			ffmpegArgs = []string{"-i", "-", "-c:a", "libfdk_aac", "-b:a", "256k", "-f", M4A, "pipe:"}
			// works, but gotta compile ffmpeg on server with special options
		} else if rv.Format == OGG {
			ffmpegArgs = []string{"-i", "-", "-c:a", "libvorbis", "-qscale:a", "6", "-f", OGG, "pipe:"}
		} else {
			log.Println("unsupported output format requested. break some rools.")
			ffmpegArgs = []string{"-i", "-", "-f", "mp3", "-ab", "256k", "pipe:"}
		}
		log.Println("running ffmpeg", strings.Join(ffmpegArgs, " "))
		ffmpegCommand := exec.Command(ffmpeg, ffmpegArgs...)

		ffmpegCommand.Stdin, _ = soxCommand.StdoutPipe()
		ffmpegCommand.Stdout = w
		ffmpegCommand.Stderr = os.Stdout

		ffmpegCommand.Start()
		soxCommand.Run()
		ffmpegCommand.Wait()
	}
}

func DownloadAndEncode(slices []TimeSlice, rv RequestVars) string {
	// Download all here. Could be done concurrently while prev slice is streaming
	DownloadAllAudio(slices)

	// Build cmds
	soxPath, err := exec.LookPath("sox")
	check(err)
	log.Println("using sox " + soxPath)
	ffmpegPath, err := exec.LookPath("ffmpeg")
	check(err)
	log.Println("using ffmpeg " + ffmpegPath)

	audioName := fmt.Sprintf("channels_%s_%d_%d.m4a", strings.Join(rv.Channels, "."), rv.Start, rv.Duration)
	outFile := path.Join(clipDir, audioName)

	// Merge and concat all files
	// sox "|sox -m in1 in2 -p" "|sox -m in3 in4 -p" -p
	// Use shell for sox to handle funky args
	soxArgs := []string{"sox"}
	for _, slice := range slices {
		var chunkPaths []string
		var cmdStr string
		// Get files for this slice
		for _, ch := range slice.chunks {
			chunkPaths = append(chunkPaths, ch.localPath)
		}
		// Only merge with multiple files
		if len(chunkPaths) == 1 {
			cmdStr = chunkPaths[0]
		} else {
			cmdStr = fmt.Sprintf("\"| %s -m %s -p\"", "sox", strings.Join(chunkPaths, " "))
		}
		soxArgs = append(soxArgs, cmdStr) //strings.Fields(cmdStr)...
	}
	soxArgs = append(soxArgs, "-p")

	ffmpegArgs := []string{"-i", "-", "-strict", "-2", "-c:a", "aac", "-b:a", "96k", "-f", "mp4", outFile}
	ffmpegArgs = append(ffmpegArgs, "-y") // Force ovewrite

	soxCmd := strings.Join(soxArgs, " ")
	log.Println("running sox", soxCmd)
	soxCommand := exec.Command("sh", "-c", soxCmd)

	log.Println("running ffmpeg", strings.Join(ffmpegArgs, " "))
	ffmpegCommand := exec.Command(ffmpegPath, ffmpegArgs...)

	ffmpegCommand.Stdin, err = soxCommand.StdoutPipe()
	if err != nil {
		log.Fatal("Cant connect sox pipe")
	}
	ffmpegCommand.Stdout = os.Stdout
	ffmpegCommand.Stderr = os.Stdout
	soxCommand.Stderr = os.Stdout

	ffmpegCommand.Start()
	soxCommand.Run()
	ffmpegCommand.Wait()

	log.Println("Saved output to", outFile)
	return outFile
}
<|endoftext|>


<commit_before>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "1.1.21"
<commit_msg>Tag release v1.1.22<commit_after>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "1.1.22"
<|endoftext|>


<commit_before>/*
 * gomacro - A Go intepreter with Lisp-like macros
 *
 * Copyright (C) 2017 Massimiliano Ghilardi
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * read.go
 *
 *  Created on: Mar 12, 2017
 *      Author: Massimiliano Ghilardi
 */

package base

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	r "reflect"
)

func ReadBytes(src interface{}) []byte {
	switch s := src.(type) {
	case []byte:
		if s != nil {
			return s
		}
	case string:
		return []byte(s)
	case *bytes.Buffer:
		// is io.Reader, but src is already available in []byte form
		if s != nil {
			return s.Bytes()
		}
	case io.Reader:
		if s != nil {
			var buf bytes.Buffer
			if _, err := io.Copy(&buf, s); err != nil {
				Error(err)
			}
			return buf.Bytes()
		}
	}
	Errorf("unsupported source, cannot read from: %v <%v>", src, r.TypeOf(src))
	return nil
}

func ReadString(src interface{}) string {
	switch s := src.(type) {
	case []byte:
		if s != nil {
			return string(s)
		}
	case string:
		return s
	case *bytes.Buffer:
		// is io.Reader, but src is already available in string form
		if s != nil {
			return s.String()
		}
	case io.Reader:
		if s != nil {
			var buf bytes.Buffer
			if _, err := io.Copy(&buf, s); err != nil {
				Error(err)
			}
			return buf.String()
		}
	}
	Errorf("unsupported source, cannot read from: %v <%v>", src, r.TypeOf(src))
	return ""
}

type ReadOptions int

const (
	ReadOptShowPrompt         ReadOptions = 1 << iota
	ReadOptCollectAllComments             // continue until non-comment is found. default is to return comments one by one
)

func ReadMultiline(in *bufio.Reader, opts ReadOptions, out io.Writer, prompt string) (src string, firstToken int, err error) {
	type Mode int
	const (
		mNormal Mode = iota
		mPlus
		mMinus
		mRune
		mString
		mRuneEscape
		mStringEscape
		mRawString
		mSlash
		mHash
		mLineComment
		mComment
		mCommentStar
		mTilde
	)
	mode := mNormal
	paren := 0
	optPrompt := opts&ReadOptShowPrompt != 0
	optAllComments := opts&ReadOptCollectAllComments != 0
	ignorenl := false
	firstToken = -1

	if optPrompt {
		fmt.Fprint(out, prompt)
	}
	// comments do not reset ignorenl
	resetnl := func(paren int, mode Mode) bool {
		return paren != 0 ||
			(mode != mNormal && mode != mSlash && mode != mHash &&
				mode != mLineComment && mode != mComment && mode != mCommentStar)
	}
	var line, buf []byte

	for {
		line, err = in.ReadBytes('\n')
		for i, ch := range line {
			Debugf("ReadMultiline: found %q, mode = %d, ignorenl = %t", ch, mode, ignorenl)
			switch mode {
			case mPlus, mMinus:
				if ch == '+' {
					if mode == mPlus {
						mode = mNormal
					} else {
						mode = mPlus
					}
					break
				} else if ch == '-' {
					if mode == mMinus {
						mode = mNormal
					} else {
						mode = mMinus
					}
					break
				}
				mode = mNormal
				ignorenl = true
				if ch <= ' ' {
					continue
				}
				fallthrough
			case mNormal:
				switch ch {
				case '(', '[', '{':
					paren++
				case ')', ']', '}':
					paren--
				case '\'':
					mode = mRune
				case '"':
					mode = mString
				case '`':
					mode = mRawString
				case '/':
					mode = mSlash
					continue // no tokens yet
				case '#':
					mode = mHash // support #! line comments
					continue     // no tokens yet
				case '~':
					mode = mTilde
				case '!', '%', '&', '*', ',', '.', '<', '=', '>', '^', '|':
					ignorenl = paren == 0
				case '+':
					ignorenl = false
					if paren == 0 {
						mode = mPlus
					}
				case '-':
					ignorenl = false
					if paren == 0 {
						mode = mMinus
					}
				default:
					if ch <= ' ' {
						continue // not a token
					}
					ignorenl = false // found a token
				}
			case mRune:
				switch ch {
				case '\\':
					mode = mRuneEscape
				case '\'':
					mode = mNormal
				default:
					if ch < ' ' {
						return merge(buf, line[:i]), firstToken, invalidChar(ch, "rune")
					}
				}
			case mRuneEscape:
				if ch < ' ' {
					return merge(buf, line[:i]), firstToken, invalidChar(ch, "rune")
				}
				mode = mRune
			case mString:
				switch ch {
				case '\\':
					mode = mStringEscape
				case '"':
					mode = mNormal
				default:
					if ch < ' ' {
						return merge(buf, line[:i]), firstToken, invalidChar(ch, "string")
					}
				}
			case mStringEscape:
				if ch < ' ' {
					return merge(buf, line[:i]), firstToken, invalidChar(ch, "string")
				}
				mode = mString
			case mRawString:
				switch ch {
				case '`':
					mode = mNormal
				}
			case mSlash:
				switch ch {
				case '/':
					mode = mLineComment
					continue // no tokens
				case '*':
					mode = mComment
					continue // no tokens
				default:
					mode = mNormal
					if paren == 0 {
						ignorenl = true
					}
					if firstToken < 0 {
						firstToken = len(buf) + i - 1
					}
				}
			case mHash:
				switch ch {
				case '!':
					mode = mLineComment
					line[i-1] = '/'
					line[i] = '/'
					continue // no tokens
				default:
					mode = mNormal
					if firstToken < 0 {
						firstToken = len(buf) + i - 1
					}
				}
			case mLineComment:
				continue
			case mComment:
				switch ch {
				case '*':
					mode = mCommentStar
				}
				continue
			case mCommentStar:
				switch ch {
				case '/':
					mode = mNormal
				default:
					mode = mComment
				}
				continue
			case mTilde:
				mode = mNormal
			}
			// Debugf("ReadMultiline: mode = %d, ignorenl = %t, resetnl = %t", mode, ignorenl, resetnl(paren, mode))
			if resetnl(paren, mode) {
				ignorenl = false
				// Debugf("ReadMultiline: cleared ignorenl")
			}
			if firstToken < 0 {
				firstToken = len(buf) + i
				// Debugf("ReadMultiline: setting firstToken to %d, line up to it = %q", firstToken, line[:i+1])
			}
		}
		buf = append(buf, line...)
		if mode == mLineComment {
			mode = mNormal
		}
		if err != nil || paren <= 0 && !ignorenl && mode == mNormal && (firstToken >= 0 || !optAllComments) {
			break
		}
		// Debugf("ReadMultiline: continuing, mode = %d, paren == %d, ignorenl = %t", mode, paren, ignorenl)
		if mode == mPlus || mode == mMinus {
			mode = mNormal
		}
		if optPrompt {
			printDots(out, 4+2*paren)
		}
	}
	if err != nil {
		if err == io.EOF && paren > 0 {
			err = errors.New("unexpected EOF")
		}
		return string(buf), firstToken, err
	}
	// Debugf("ReadMultiline: read %d bytes, firstToken at %d", len(buf), firstToken)
	// if firstToken >= 0 {
	//     Debugf("ReadMultiline: comments: %q", buf[:firstToken])
	//     Debugf("ReadMultiline: tokens: %q", buf[firstToken:])
	// } else {
	//     Debugf("ReadMultiline: comments: %q", buf)
	// }
	return string(buf), firstToken, nil
}

func merge(buf, tail []byte) string {
	return string(append(buf, tail...))
}

func invalidChar(ch byte, ctx string) error {
	return errors.New(fmt.Sprintf("unexpected character %q inside %s literal", ch, ctx))
}

func printDots(out io.Writer, count int) {
	const (
		dots  = ". . . . . . . . . . . . . . . . "
		ndots = len(dots)
	)
	for count >= ndots {
		fmt.Fprint(out, dots)
		count -= ndots
	}
	if count > 0 {
		fmt.Fprint(out, dots[0:count])
	}
}
<commit_msg>base/read.go: removed debug printf<commit_after>/*
 * gomacro - A Go intepreter with Lisp-like macros
 *
 * Copyright (C) 2017 Massimiliano Ghilardi
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * read.go
 *
 *  Created on: Mar 12, 2017
 *      Author: Massimiliano Ghilardi
 */

package base

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	r "reflect"
)

func ReadBytes(src interface{}) []byte {
	switch s := src.(type) {
	case []byte:
		if s != nil {
			return s
		}
	case string:
		return []byte(s)
	case *bytes.Buffer:
		// is io.Reader, but src is already available in []byte form
		if s != nil {
			return s.Bytes()
		}
	case io.Reader:
		if s != nil {
			var buf bytes.Buffer
			if _, err := io.Copy(&buf, s); err != nil {
				Error(err)
			}
			return buf.Bytes()
		}
	}
	Errorf("unsupported source, cannot read from: %v <%v>", src, r.TypeOf(src))
	return nil
}

func ReadString(src interface{}) string {
	switch s := src.(type) {
	case []byte:
		if s != nil {
			return string(s)
		}
	case string:
		return s
	case *bytes.Buffer:
		// is io.Reader, but src is already available in string form
		if s != nil {
			return s.String()
		}
	case io.Reader:
		if s != nil {
			var buf bytes.Buffer
			if _, err := io.Copy(&buf, s); err != nil {
				Error(err)
			}
			return buf.String()
		}
	}
	Errorf("unsupported source, cannot read from: %v <%v>", src, r.TypeOf(src))
	return ""
}

type ReadOptions int

const (
	ReadOptShowPrompt         ReadOptions = 1 << iota
	ReadOptCollectAllComments             // continue until non-comment is found. default is to return comments one by one
)

func ReadMultiline(in *bufio.Reader, opts ReadOptions, out io.Writer, prompt string) (src string, firstToken int, err error) {
	type Mode int
	const (
		mNormal Mode = iota
		mPlus
		mMinus
		mRune
		mString
		mRuneEscape
		mStringEscape
		mRawString
		mSlash
		mHash
		mLineComment
		mComment
		mCommentStar
		mTilde
	)
	mode := mNormal
	paren := 0
	optPrompt := opts&ReadOptShowPrompt != 0
	optAllComments := opts&ReadOptCollectAllComments != 0
	ignorenl := false
	firstToken = -1

	if optPrompt {
		fmt.Fprint(out, prompt)
	}
	// comments do not reset ignorenl
	resetnl := func(paren int, mode Mode) bool {
		return paren != 0 ||
			(mode != mNormal && mode != mSlash && mode != mHash &&
				mode != mLineComment && mode != mComment && mode != mCommentStar)
	}
	var line, buf []byte

	for {
		line, err = in.ReadBytes('\n')
		for i, ch := range line {
			// Debugf("ReadMultiline: found %q, mode = %d, ignorenl = %t", ch, mode, ignorenl)
			switch mode {
			case mPlus, mMinus:
				if ch == '+' {
					if mode == mPlus {
						mode = mNormal
					} else {
						mode = mPlus
					}
					break
				} else if ch == '-' {
					if mode == mMinus {
						mode = mNormal
					} else {
						mode = mMinus
					}
					break
				}
				mode = mNormal
				ignorenl = true
				if ch <= ' ' {
					continue
				}
				fallthrough
			case mNormal:
				switch ch {
				case '(', '[', '{':
					paren++
				case ')', ']', '}':
					paren--
				case '\'':
					mode = mRune
				case '"':
					mode = mString
				case '`':
					mode = mRawString
				case '/':
					mode = mSlash
					continue // no tokens yet
				case '#':
					mode = mHash // support #! line comments
					continue     // no tokens yet
				case '~':
					mode = mTilde
				case '!', '%', '&', '*', ',', '.', '<', '=', '>', '^', '|':
					ignorenl = paren == 0
				case '+':
					ignorenl = false
					if paren == 0 {
						mode = mPlus
					}
				case '-':
					ignorenl = false
					if paren == 0 {
						mode = mMinus
					}
				default:
					if ch <= ' ' {
						continue // not a token
					}
					ignorenl = false // found a token
				}
			case mRune:
				switch ch {
				case '\\':
					mode = mRuneEscape
				case '\'':
					mode = mNormal
				default:
					if ch < ' ' {
						return merge(buf, line[:i]), firstToken, invalidChar(ch, "rune")
					}
				}
			case mRuneEscape:
				if ch < ' ' {
					return merge(buf, line[:i]), firstToken, invalidChar(ch, "rune")
				}
				mode = mRune
			case mString:
				switch ch {
				case '\\':
					mode = mStringEscape
				case '"':
					mode = mNormal
				default:
					if ch < ' ' {
						return merge(buf, line[:i]), firstToken, invalidChar(ch, "string")
					}
				}
			case mStringEscape:
				if ch < ' ' {
					return merge(buf, line[:i]), firstToken, invalidChar(ch, "string")
				}
				mode = mString
			case mRawString:
				switch ch {
				case '`':
					mode = mNormal
				}
			case mSlash:
				switch ch {
				case '/':
					mode = mLineComment
					continue // no tokens
				case '*':
					mode = mComment
					continue // no tokens
				default:
					mode = mNormal
					if paren == 0 {
						ignorenl = true
					}
					if firstToken < 0 {
						firstToken = len(buf) + i - 1
					}
				}
			case mHash:
				switch ch {
				case '!':
					mode = mLineComment
					line[i-1] = '/'
					line[i] = '/'
					continue // no tokens
				default:
					mode = mNormal
					if firstToken < 0 {
						firstToken = len(buf) + i - 1
					}
				}
			case mLineComment:
				continue
			case mComment:
				switch ch {
				case '*':
					mode = mCommentStar
				}
				continue
			case mCommentStar:
				switch ch {
				case '/':
					mode = mNormal
				default:
					mode = mComment
				}
				continue
			case mTilde:
				mode = mNormal
			}
			// Debugf("ReadMultiline: mode = %d, ignorenl = %t, resetnl = %t", mode, ignorenl, resetnl(paren, mode))
			if resetnl(paren, mode) {
				ignorenl = false
				// Debugf("ReadMultiline: cleared ignorenl")
			}
			if firstToken < 0 {
				firstToken = len(buf) + i
				// Debugf("ReadMultiline: setting firstToken to %d, line up to it = %q", firstToken, line[:i+1])
			}
		}
		buf = append(buf, line...)
		if mode == mLineComment {
			mode = mNormal
		}
		if err != nil || paren <= 0 && !ignorenl && mode == mNormal && (firstToken >= 0 || !optAllComments) {
			break
		}
		// Debugf("ReadMultiline: continuing, mode = %d, paren == %d, ignorenl = %t", mode, paren, ignorenl)
		if mode == mPlus || mode == mMinus {
			mode = mNormal
		}
		if optPrompt {
			printDots(out, 4+2*paren)
		}
	}
	if err != nil {
		if err == io.EOF && paren > 0 {
			err = errors.New("unexpected EOF")
		}
		return string(buf), firstToken, err
	}
	// Debugf("ReadMultiline: read %d bytes, firstToken at %d", len(buf), firstToken)
	// if firstToken >= 0 {
	//     Debugf("ReadMultiline: comments: %q", buf[:firstToken])
	//     Debugf("ReadMultiline: tokens: %q", buf[firstToken:])
	// } else {
	//     Debugf("ReadMultiline: comments: %q", buf)
	// }
	return string(buf), firstToken, nil
}

func merge(buf, tail []byte) string {
	return string(append(buf, tail...))
}

func invalidChar(ch byte, ctx string) error {
	return errors.New(fmt.Sprintf("unexpected character %q inside %s literal", ch, ctx))
}

func printDots(out io.Writer, count int) {
	const (
		dots  = ". . . . . . . . . . . . . . . . "
		ndots = len(dots)
	)
	for count >= ndots {
		fmt.Fprint(out, dots)
		count -= ndots
	}
	if count > 0 {
		fmt.Fprint(out, dots[0:count])
	}
}
<|endoftext|>


<commit_before>package gparselib

import (
	"strings"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestWhere(t *testing.T) {
	src := &SourceData{Name: "file1", content: "content\nline2\nline3\nand4\n",
		pos: 15, wherePrevNl: 13, whereLine: 3}

	Convey("Searching forward, ...", t, func() {
		Convey(`... should find a position in the same line.`, func() {
			where := where(src, 14)

			So(where, ShouldContainSubstring, "File '"+src.Name+"'")
			So(where, ShouldContainSubstring, "line 3")
			So(where, ShouldContainSubstring, "column 1")
			So(where, ShouldEndWith, "\nline3\n")
		})

		Convey(`... should find position at the end.`, func() {
			where := where(src, len(src.content)-1)

			So(where, ShouldContainSubstring, "line 4")
			So(where, ShouldContainSubstring, "column 5")
			So(where, ShouldEndWith, "\nand4\n")
		})
	})

	Convey("Searching backward, ...", t, func() {
		Convey(`... should find a position in the previous line.`, func() {
			where := where(src, 13)

			So(where, ShouldContainSubstring, "File '"+src.Name+"'")
			So(where, ShouldContainSubstring, "line 2")
			So(where, ShouldContainSubstring, "column 6")
			So(where, ShouldEndWith, "\nline2\n")
		})

		Convey(`... should find start position.`, func() {
			where := where(src, 0)

			So(where, ShouldContainSubstring, "line 1")
			So(where, ShouldContainSubstring, "column 1")
			So(where, ShouldEndWith, "\ncontent\n")
		})
	})

	Convey("Searching in empty content, ...", t, func() {
		src := &SourceData{Name: "empty", content: "",
			pos: 0, wherePrevNl: -1, whereLine: 1}

		Convey(`... should find start position.`, func() {
			where := where(src, 0)

			So(where, ShouldContainSubstring, "File '"+src.Name+"'")
			So(where, ShouldContainSubstring, "line 1")
			So(where, ShouldContainSubstring, "column 1")
			So(where, ShouldEndWith, "\n")
		})
	})
}

func TestCreateUnmatchedResult(t *testing.T) {
	specs := []struct {
		givenParseData       *ParseData
		givenErrOffset       int
		givenErrMsg          string
		expectedResultPos    int
		expectedResultErrPos int
	}{
		{
			givenParseData: &ParseData{
				Source: SourceData{
					Name:        "file1",
					content:     "content\nline2\nline3\nand4\n",
					pos:         15,
					wherePrevNl: 13,
					whereLine:   3,
				},
			},
			givenErrOffset:       0,
			givenErrMsg:          "Bust1",
			expectedResultPos:    15,
			expectedResultErrPos: 15,
		}, {
			givenParseData: &ParseData{
				Source: SourceData{
					Name:        "file1",
					content:     "content\nline2\nline3\nand4\n",
					pos:         15,
					wherePrevNl: 13,
					whereLine:   3,
				},
			},
			givenErrOffset:       3,
			givenErrMsg:          "Bust2",
			expectedResultPos:    15,
			expectedResultErrPos: 18,
		},
	}

	for i, spec := range specs {
		t.Logf("Test run: %d", i)
		pd := spec.givenParseData
		createUnmatchedResult(pd, spec.givenErrOffset, spec.givenErrMsg, nil)
		if pd.Result == nil {
			t.Errorf("The result shouldn't be nil but it is!")
			break
		}
		if pd.Result.Pos != spec.expectedResultPos {
			t.Errorf(
				"The result position should be %d, but is %d.",
				spec.expectedResultPos,
				spec.givenParseData.Result.Pos,
			)
		}
		if pd.Result.ErrPos != spec.expectedResultErrPos {
			t.Errorf(
				"The result error position should be %d, but is %d.",
				spec.expectedResultErrPos,
				pd.Result.ErrPos,
			)
		}
		if pd.Result.Text != "" {
			t.Errorf(
				"The result text should be empty, but is '%s'.",
				pd.Result.Text,
			)
		}
		if pd.Result.Value != nil {
			t.Errorf(
				"The result value should be nil but it is: %#v",
				pd.Result.Value,
			)
		}
		if !pd.Result.HasError() {
			t.Errorf("The result should contain an error, but it doesn't!")
		}
		if len(pd.Result.Feedback) != 1 {
			t.Errorf(
				"Expected length of feedback to be 1, but it is: %d",
				len(pd.Result.Feedback),
			)
		}
		if !strings.Contains(pd.Result.Feedback[0].String(), spec.givenErrMsg) {
			t.Errorf(
				"Expected error message containing '%s', but got: '%s'",
				spec.givenErrMsg,
				pd.Result.Feedback[0].String(),
			)
		}
	}
}

func TestCreateMatchedResult(t *testing.T) {
	specs := []struct {
		givenParseData       *ParseData
		givenN               int
		expectedResultPos    int
		expectedResultErrPos int
		expectedResultText   string
	}{
		{
			givenParseData: &ParseData{
				Source: SourceData{
					Name:        "file1",
					content:     "content\nline2\nline3\nand4\n",
					pos:         15,
					wherePrevNl: 13,
					whereLine:   3,
				},
			},
			givenN:               0,
			expectedResultPos:    15,
			expectedResultErrPos: -1,
			expectedResultText:   "",
		}, {
			givenParseData: &ParseData{
				Source: SourceData{
					Name:        "file1",
					content:     "content\nline2\nline3\nand4\n",
					pos:         15,
					wherePrevNl: 13,
					whereLine:   3,
				},
			},
			givenN:               4,
			expectedResultPos:    15,
			expectedResultErrPos: -1,
			expectedResultText:   "ine3",
		},
	}

	for i, spec := range specs {
		t.Logf("Test run: %d", i)
		pd := spec.givenParseData
		createMatchedResult(pd, spec.givenN)
		if pd.Result == nil {
			t.Errorf("The result shouldn't be nil but it is!")
			break
		}
		if pd.Result.Pos != spec.expectedResultPos {
			t.Errorf(
				"The result position should be %d, but is %d.",
				spec.expectedResultPos,
				spec.givenParseData.Result.Pos,
			)
		}
		if pd.Result.ErrPos != spec.expectedResultErrPos {
			t.Errorf(
				"The result error position should be %d, but is %d.",
				spec.expectedResultErrPos,
				pd.Result.ErrPos,
			)
		}
		if pd.Result.Text != spec.expectedResultText {
			t.Errorf(
				"The result text should be %s, but is %s.",
				spec.expectedResultText,
				pd.Result.Text,
			)
		}
		if pd.Result.Value != nil {
			t.Errorf(
				"The result value should be nil but it is: %#v",
				pd.Result.Value,
			)
		}
	}
}

func TestMinMax(t *testing.T) {
	specs := []struct {
		givenA         int
		givenB         int
		expectedResult int
		actualResult   int
		name           string
	}{
		{givenA: 1, givenB: 2, expectedResult: 2, actualResult: max(1, 2), name: "max"},
		{givenA: 2, givenB: 1, expectedResult: 2, actualResult: max(2, 1), name: "max"},
		{givenA: 1, givenB: 2, expectedResult: 1, actualResult: min(1, 2), name: "min"},
		{givenA: 2, givenB: 1, expectedResult: 1, actualResult: min(2, 1), name: "min"},
	}
	for _, spec := range specs {
		if spec.actualResult != spec.expectedResult {
			t.Errorf("%s(%d, %d) != %d", spec.name, spec.givenA, spec.givenB, spec.expectedResult)
		}
	}
}
<commit_msg>Standardize test for 'where'<commit_after>package gparselib

import (
	"strings"
	"testing"
)

func TestWhere(t *testing.T) {
	src := &SourceData{
		Name:        "file1",
		content:     "content\nline2\nline3\nand4\n",
		pos:         15,
		wherePrevNl: 13,
		whereLine:   3,
	}

	specs := []struct {
		givenSourceData *SourceData
		givenPosition   int
		expectedStrings []string
	}{
		{
			givenSourceData: src,
			givenPosition:   14,
			expectedStrings: []string{
				"File 'file1'",
				"line 3",
				"column 1",
				"\nline3\n",
			},
		}, {
			givenSourceData: src,
			givenPosition:   len(src.content) - 1,
			expectedStrings: []string{
				"File 'file1'",
				"line 4",
				"column 5",
				"\nand4\n",
			},
		}, {
			givenSourceData: src,
			givenPosition:   13,
			expectedStrings: []string{
				"File 'file1'",
				"line 2",
				"column 6",
				"\nline2\n",
			},
		}, {
			givenSourceData: src,
			givenPosition:   0,
			expectedStrings: []string{
				"File 'file1'",
				"line 1",
				"column 1",
				"\ncontent\n",
			},
		}, {
			givenSourceData: &SourceData{
				Name:        "empty",
				content:     "",
				pos:         0,
				wherePrevNl: -1,
				whereLine:   1,
			},
			givenPosition: 0,
			expectedStrings: []string{
				"File 'empty'",
				"line 1",
				"column 1",
				"\n\n",
			},
		},
	}

	for i, spec := range specs {
		t.Logf("Test run: %d", i)
		w := where(spec.givenSourceData, spec.givenPosition)
		for _, s := range spec.expectedStrings {
			if !strings.Contains(w, s) {
				t.Errorf(
					"Expected where string containing '%s', but got: '%s'",
					s,
					w,
				)
			}
		}
	}
}

func TestCreateUnmatchedResult(t *testing.T) {
	specs := []struct {
		givenParseData       *ParseData
		givenErrOffset       int
		givenErrMsg          string
		expectedResultPos    int
		expectedResultErrPos int
	}{
		{
			givenParseData: &ParseData{
				Source: SourceData{
					Name:        "file1",
					content:     "content\nline2\nline3\nand4\n",
					pos:         15,
					wherePrevNl: 13,
					whereLine:   3,
				},
			},
			givenErrOffset:       0,
			givenErrMsg:          "Bust1",
			expectedResultPos:    15,
			expectedResultErrPos: 15,
		}, {
			givenParseData: &ParseData{
				Source: SourceData{
					Name:        "file1",
					content:     "content\nline2\nline3\nand4\n",
					pos:         15,
					wherePrevNl: 13,
					whereLine:   3,
				},
			},
			givenErrOffset:       3,
			givenErrMsg:          "Bust2",
			expectedResultPos:    15,
			expectedResultErrPos: 18,
		},
	}

	for i, spec := range specs {
		t.Logf("Test run: %d", i)
		pd := spec.givenParseData
		createUnmatchedResult(pd, spec.givenErrOffset, spec.givenErrMsg, nil)
		if pd.Result == nil {
			t.Errorf("The result shouldn't be nil but it is!")
			break
		}
		if pd.Result.Pos != spec.expectedResultPos {
			t.Errorf(
				"The result position should be %d, but is %d.",
				spec.expectedResultPos,
				spec.givenParseData.Result.Pos,
			)
		}
		if pd.Result.ErrPos != spec.expectedResultErrPos {
			t.Errorf(
				"The result error position should be %d, but is %d.",
				spec.expectedResultErrPos,
				pd.Result.ErrPos,
			)
		}
		if pd.Result.Text != "" {
			t.Errorf(
				"The result text should be empty, but is '%s'.",
				pd.Result.Text,
			)
		}
		if pd.Result.Value != nil {
			t.Errorf(
				"The result value should be nil but it is: %#v",
				pd.Result.Value,
			)
		}
		if !pd.Result.HasError() {
			t.Errorf("The result should contain an error, but it doesn't!")
		}
		if len(pd.Result.Feedback) != 1 {
			t.Errorf(
				"Expected length of feedback to be 1, but it is: %d",
				len(pd.Result.Feedback),
			)
		}
		if !strings.Contains(pd.Result.Feedback[0].String(), spec.givenErrMsg) {
			t.Errorf(
				"Expected error message containing '%s', but got: '%s'",
				spec.givenErrMsg,
				pd.Result.Feedback[0].String(),
			)
		}
	}
}

func TestCreateMatchedResult(t *testing.T) {
	specs := []struct {
		givenParseData       *ParseData
		givenN               int
		expectedResultPos    int
		expectedResultErrPos int
		expectedResultText   string
	}{
		{
			givenParseData: &ParseData{
				Source: SourceData{
					Name:        "file1",
					content:     "content\nline2\nline3\nand4\n",
					pos:         15,
					wherePrevNl: 13,
					whereLine:   3,
				},
			},
			givenN:               0,
			expectedResultPos:    15,
			expectedResultErrPos: -1,
			expectedResultText:   "",
		}, {
			givenParseData: &ParseData{
				Source: SourceData{
					Name:        "file1",
					content:     "content\nline2\nline3\nand4\n",
					pos:         15,
					wherePrevNl: 13,
					whereLine:   3,
				},
			},
			givenN:               4,
			expectedResultPos:    15,
			expectedResultErrPos: -1,
			expectedResultText:   "ine3",
		},
	}

	for i, spec := range specs {
		t.Logf("Test run: %d", i)
		pd := spec.givenParseData
		createMatchedResult(pd, spec.givenN)
		if pd.Result == nil {
			t.Errorf("The result shouldn't be nil but it is!")
			break
		}
		if pd.Result.Pos != spec.expectedResultPos {
			t.Errorf(
				"The result position should be %d, but is %d.",
				spec.expectedResultPos,
				spec.givenParseData.Result.Pos,
			)
		}
		if pd.Result.ErrPos != spec.expectedResultErrPos {
			t.Errorf(
				"The result error position should be %d, but is %d.",
				spec.expectedResultErrPos,
				pd.Result.ErrPos,
			)
		}
		if pd.Result.Text != spec.expectedResultText {
			t.Errorf(
				"The result text should be %s, but is %s.",
				spec.expectedResultText,
				pd.Result.Text,
			)
		}
		if pd.Result.Value != nil {
			t.Errorf(
				"The result value should be nil but it is: %#v",
				pd.Result.Value,
			)
		}
	}
}

func TestMinMax(t *testing.T) {
	specs := []struct {
		givenA         int
		givenB         int
		expectedResult int
		actualResult   int
		name           string
	}{
		{givenA: 1, givenB: 2, expectedResult: 2, actualResult: max(1, 2), name: "max"},
		{givenA: 2, givenB: 1, expectedResult: 2, actualResult: max(2, 1), name: "max"},
		{givenA: 1, givenB: 2, expectedResult: 1, actualResult: min(1, 2), name: "min"},
		{givenA: 2, givenB: 1, expectedResult: 1, actualResult: min(2, 1), name: "min"},
	}
	for _, spec := range specs {
		if spec.actualResult != spec.expectedResult {
			t.Errorf("%s(%d, %d) != %d", spec.name, spec.givenA, spec.givenB, spec.expectedResult)
		}
	}
}
<|endoftext|>


<commit_before>package app

import (
	"appengine"
	"appengine/datastore"
	"ebird"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"io"
	"io/ioutil"
	"regexp"
)

const (
	feedPrefix = "/user/"
)

func init() {
	http.HandleFunc(feedPrefix, serveFeed)
	http.HandleFunc("/debug", debug)
}

type User struct {
	Login string `datastore:",noindex"`
	Password string `datastore:",noindex"`
}

func userKey(context appengine.Context, login string) *datastore.Key {
	return datastore.NewKey(context, "User", login, 0, nil)
}

func serveFeed(w http.ResponseWriter, r *http.Request) {
	login, err := parseUser(r.URL.Path)
	if (err != nil) {
		http.NotFound(w, r)
		return
	}

	context := appengine.NewContext(r)

	user := new(User)
	err = datastore.Get(context, userKey(context, login), user)
	if err != nil {
		log(w, err)
		return
	}

	log(w, user.Login)

	client := ebird.NewClient(context)

	form := url.Values{}
	form.Set("j_username", user.Login)
	form.Set("j_password", user.Password)
	form.Set("cmd", "login")
	resp, err := client.PostForm("https://ebird.org/ebird/j_acegi_security_check", form)
	if (err != nil) {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	resp, err = client.Get("http://ebird.org/ebird/eBirdReports?cmd=subReport")
	if (err != nil) {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	data, err := ioutil.ReadAll(resp.Body)
	log(w, string(data))

	_, err = client.Get("http://ebird.org/ebird/j_acegi_logout")
	if (err != nil) {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func parseUser(path string) (user string, err error) {
	user = path[len(feedPrefix):]
	if (!regexp.MustCompile("^[a-zA-Z0-9.-_]+$").MatchString(user)) {
		err = errors.New("Invalid user path")
	}
	return
}

func log(w io.Writer, value interface{}) {
	fmt.Fprintf(w, "%+v\n", value)
}
<commit_msg>Rename Password to ObfuscatedPassword and add comments.<commit_after>package app

import (
	"appengine"
	"appengine/datastore"
	"ebird"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"io"
	"io/ioutil"
	"regexp"
)

const (
	feedPrefix = "/user/"
)

func init() {
	http.HandleFunc(feedPrefix, serveFeed)
	http.HandleFunc("/debug", debug)
}

type User struct {
	Login string `datastore:",noindex"`
	ObfuscatedPassword []byte `datastore:",noindex"`
}

func userKey(context appengine.Context, login string) *datastore.Key {
	return datastore.NewKey(context, "User", login, 0, nil)
}

func serveFeed(w http.ResponseWriter, r *http.Request) {
	// 1. Lookup user.
        // 2. Fetch checklist data.
	//   2.1. If not refreshed recently, download checklist ids.
	//   2.2. Look up missing checklists.
	// 3. Transform checklists into RSS/Atom elements.

	login, err := parseUser(r.URL.Path)
	if (err != nil) {
		http.NotFound(w, r)
		return
	}

	context := appengine.NewContext(r)

	user := new(User)
	err = datastore.Get(context, userKey(context, login), user)
	if err != nil {
		log(w, err)
		return
	}

	log(w, user.Login)

	client := ebird.NewClient(context)

	form := url.Values{}
	form.Set("j_username", user.Login)
	form.Set("j_password", user.Password)
	form.Set("cmd", "login")
	resp, err := client.PostForm("https://ebird.org/ebird/j_acegi_security_check", form)
	if (err != nil) {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	resp, err = client.Get("http://ebird.org/ebird/eBirdReports?cmd=subReport")
	if (err != nil) {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	data, err := ioutil.ReadAll(resp.Body)
	log(w, string(data))

	_, err = client.Get("http://ebird.org/ebird/j_acegi_logout")
	if (err != nil) {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func parseUser(path string) (user string, err error) {
	user = path[len(feedPrefix):]
	if (!regexp.MustCompile("^[a-zA-Z0-9.-_]+$").MatchString(user)) {
		err = errors.New("Invalid user path")
	}
	return
}

func log(w io.Writer, value interface{}) {
	fmt.Fprintf(w, "%+v\n", value)
}
<|endoftext|>


<commit_before>package handlers

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

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

// TestAsciiCatRespRecorder uses net/http/httptest ResponseRecorder
// (https://godoc.org/net/http/httptest#ResponseRecorder) to test the AsciiCat
// handler directly.
//
// ResponseRecorder is useful for direct testing of handlers,
// but doesn't provide a complete solution when the router itself handles complex logic.
// See TestGetIssuesTestSrv in get_issues_test.go for an example of testing complex router logic
func TestAsciiCatRespRecorder(t *testing.T) {
	w := httptest.NewRecorder()
	r, err := http.NewRequest("GET", "/ascci_cat", nil)
	if err != nil {
		t.Fatalf("error constructing test HTTP request [%s]", err)
	}
	ghClient := github.NewClient(nil)
	AsciiCat(ghClient).ServeHTTP(w, r)
	if w.Code != http.StatusOK {
		t.Fatalf("expected code %d, got %d", http.StatusOK, w.Code)
	}
	bodyStr := string(w.Body.Bytes())
	if len(bodyStr) <= 0 {
		t.Fatalf("expected non-empty response body")
	}
	expectedCat, _, err := ghClient.Octocat("Hello, Go In 5 Minutes Viewer!")
	if err != nil {
		t.Fatalf("error getting expected octocat string [%s]", err)
	}
	if bodyStr != expectedCat {
		t.Fatalf("got unexpected octocat string [%s]", bodyStr)
	}
}
<commit_msg>typo in code<commit_after>package handlers

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

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

// TestAsciiCatRespRecorder uses net/http/httptest ResponseRecorder
// (https://godoc.org/net/http/httptest#ResponseRecorder) to test the AsciiCat
// handler directly.
//
// ResponseRecorder is useful for direct testing of handlers,
// but doesn't provide a complete solution when the router itself handles complex logic.
// See TestGetIssuesTestSrv in get_issues_test.go for an example of testing complex router logic
func TestAsciiCatRespRecorder(t *testing.T) {
	w := httptest.NewRecorder()
	r, err := http.NewRequest("GET", "/ascii_cat", nil)
	if err != nil {
		t.Fatalf("error constructing test HTTP request [%s]", err)
	}
	ghClient := github.NewClient(nil)
	AsciiCat(ghClient).ServeHTTP(w, r)
	if w.Code != http.StatusOK {
		t.Fatalf("expected code %d, got %d", http.StatusOK, w.Code)
	}
	bodyStr := string(w.Body.Bytes())
	if len(bodyStr) <= 0 {
		t.Fatalf("expected non-empty response body")
	}
	expectedCat, _, err := ghClient.Octocat("Hello, Go In 5 Minutes Viewer!")
	if err != nil {
		t.Fatalf("error getting expected octocat string [%s]", err)
	}
	if bodyStr != expectedCat {
		t.Fatalf("got unexpected octocat string [%s]", bodyStr)
	}
}
<|endoftext|>