<commit_before>package artisan

import (
    "strings"
    "os"
    "log"
)

func checkFileIsExist(filename string) (bool) {
    var exist = true;
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        exist = false;
    }
    return exist;
}

func CreateProject(name string) {

    pathApp := "controller,exception,middleware,model,router,tool"

    for _, path := range strings.Split(pathApp, ",") {
        path = name + "/src/app/" + path;

        os.MkdirAll(path, os.ModePerm)
    }
    os.MkdirAll(name + "/src/config", os.ModePerm)

    files := map[string]string{}

    // Index
    files["index.go"] = "package main\n\nimport\n(\n    \"app/router\"\n    \"net/http\"\n    \"log\"\n    \"github.com/bysir-zl/bygo/bygo\"\n    \"app/exception\"\n)\n\nfunc main() {\n\n    apiHandle := bygo.NewApiHandler()\n\n    apiHandle.ConfigRouter(\"api\", router.Init)\n    apiHandle.ConfigExceptHandler(exception.Handler)\n    apiHandle.Init()\n\n    http.Handle(\"/api/\", apiHandle);\n    http.Handle(\"/\", http.FileServer(http.Dir(\"./dist\")))\n\n    log.Println(\"server start success\")\n\n    err := http.ListenAndServe(\":81\", nil)\n\n    if err != nil {\n        log.Println(err)\n    }\n}"
    // router
    files["app/router/router.go"] = "package router\n\nimport (\n    \"app/middleware\"\n    \"app/controller\"\n    \"github.com/bysir-zl/bygo/http\"\n)\n\nfunc Init(node *http.RouterNode) {\n    node.Middleware(&middleware.HeaderMiddleware{}) // 为当前节点添加上中间件\n\n    node.Get(\"/\", func(request *http.Request, p http.Response) http.ResponseData {\n        return http.NewRespDataHtml(404, \"blank\")\n    })\n\n    node.Controller(\"index\", &controller.IndexController{})\n}\n"
    // IndexController
    files["app/controller/IndexController.go"] = "package controller\n\nimport (\n    \"fmt\"\n    \"strings\"\n    \"github.com/bysir-zl/bygo/http\"\n)\n\ntype IndexController struct{}\n\nfunc (p IndexController) Index(r *http.Request, s http.Response) http.ResponseData {\n\n    return http.NewRespDataHtml(200, \"welcome to use bygo!\" + \"

\" +\n        \"Url: \" + r.Router.Url + \"
\" +\n        \"Handler: \" + r.Router.Handler + \"
\" +\n        \"RouterParams : \" + strings.Join(r.Router.Params, \",\") + \"
\" +\n        \"Input : \" + fmt.Sprint(r.Input.All()) + \"
\" +\n        \"Header : \" + fmt.Sprint(r.Header) + \"
\" +\n        \"\")\n}"

    // HeaderMiddleware
    files["app/middleware/HeaderMiddleware.go"] = "package middleware\n\nimport (\n    \"github.com/bysir-zl/bygo/http\"\n)\n\ntype HeaderMiddleware struct {\n\n}\n\nfunc (p HeaderMiddleware) HandlerBefore(s http.SessionContainer) (needStop bool, data http.ResponseData) {\n    s.Request.Input.Set(\"ext\", \"from middleware\")\n    return false, http.NewRespDataHtml(0, \"\")\n}\n\nfunc (p HeaderMiddleware) HandlerAfter(s http.SessionContainer) (needStop bool, data http.ResponseData) {\n\n    response := s.Response\n    response.ResponseData.Body = response.ResponseData.Body + \"

 i am from middleware\"\n    response.AddHeader(\"Access-Control-Allow-Origin\", \"*\") // 添加上允许跨域\n    response.AddHeader(\"Access-Control-Allow-Headers\", \"X_TOKEN\") // 添加上允许的头,用来身份验证\n\n    return false, http.NewRespDataHtml(0, \"\")\n}\n"

    // HeaderMiddleware
    files["app/exception/Exception.go"] = "package exception\n\nimport (\n    \"github.com/bysir-zl/bygo/bean\"\n    \"github.com/bysir-zl/bygo/http\"\n)\n\n// 将报错的Exception处理成Response返回。在这里你可以判断e.Code统一处理错误,比如上报code==500的错误\nfunc Handler(c http.SessionContainer, e http.Exceptions) http.ResponseData {\n    return http.NewRespDataJson(200, bean.ApiData{Code:e.Code, Msg:e.Message})\n}\n"

    // UserModel
    files["app/model/UserModel.go"] = "package model\n\ntype UserModel struct {\n    Table    string  `db:\"user\" json:\"-\"`\n    Connect  string `db:\"default\" json:\"-\"`\n\n    Id       int64 `name:\"id\" pk:\"auto\" json:\"id\"`\n    Password string `name:\"password\" json:\"password,omitempty\"`\n    UserName string `name:\"username\" json:\"username\"`\n\n    CreateAt string `name:\"create_at\" auto:\"time,insert\" json:\"create_at\"`\n    UpdateAt string `name:\"update_at\" auto:\"time,update|insert\" json:\"update_at\"`\n\n    Token    string `json:\"token,omitempty\"`\n}\n    "

    // config - app
    files["config/app.go"] = "package config\n\nvar Debug = true\n"

    // config - chche
    files["config/chche.go"] = "package config\n\nvar CacheDriver = \"redis\"\n"
    // config - db
    files["config/db.go"] = "package config\n\nimport \"github.com/bysir-zl/bygo/db\"\n\nvar DbConfigs map[string]db.DbConfig = map[string]db.DbConfig{}\n\nfunc init() {\n    if !Debug {\n        DbConfigs[\"default\"] = db.DbConfig{\n            Driver:\"mysql\",\n            Host:\"localhost\",\n            Port:3306,\n            Name:\"password\",\n            User:\"root\",\n            Password:\"zhangliang\",\n        }\n    } else {\n        DbConfigs[\"default\"] = db.DbConfig{\n            Driver:\"mysql\",\n            Host:\"localhost\",\n            Port:3306,\n            Name:\"password\",\n            User:\"root\",\n            Password:\"\",\n        }\n    }\n}\n"
    // config - redis
    files["config/redis.go"] = "package config\n\nvar RedisHost = \"127.0.0.1:6379\"\n"

    // 写入文件
    for filename, content := range files {
        filename = name + "/src/" + filename
        var f *os.File
        var err error
        if checkFileIsExist(filename) {
            f, err = os.OpenFile(filename, os.O_RDWR, os.ModePerm)
            if err != nil {
                panic(err)
            }

        } else {
            f, err = os.Create(filename)
            if err != nil {
                panic(err)
            }
        }

        f.Write([]byte(content))
    }

    log.Print("create success")
}<commit_msg>fix bug<commit_after>package artisan

import (
    "strings"
    "os"
    "log"
)

func checkFileIsExist(filename string) (bool) {
    var exist = true;
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        exist = false;
    }
    return exist;
}

func CreateProject(name string) {

    pathApp := "controller,exception,middleware,model,router,tool"

    for _, path := range strings.Split(pathApp, ",") {
        path = name + "/src/app/" + path;

        os.MkdirAll(path, os.ModePerm)
    }
    os.MkdirAll(name + "/src/config", os.ModePerm)

    files := map[string]string{}

    // Index
    files["index.go"] = "package main\n\nimport\n(\n    \"app/router\"\n    \"net/http\"\n    \"log\"\n    \"github.com/bysir-zl/bygo/bygo\"\n    \"app/exception\"\n)\n\nfunc main() {\n\n    apiHandle := bygo.NewApiHandler()\n\n    apiHandle.ConfigRouter(\"api\", router.Init)\n    apiHandle.ConfigExceptHandler(exception.Handler)\n    apiHandle.Init()\n\n    http.Handle(\"/api/\", apiHandle);\n    http.Handle(\"/\", http.FileServer(http.Dir(\"./dist\")))\n\n    log.Println(\"server start success\")\n\n    err := http.ListenAndServe(\":81\", nil)\n\n    if err != nil {\n        log.Println(err)\n    }\n}"
    // router
    files["app/router/router.go"] = "package router\n\nimport (\n    \"app/middleware\"\n    \"app/controller\"\n    \"github.com/bysir-zl/bygo/http\"\n)\n\nfunc Init(node *http.RouterNode) {\n    node.Middleware(&middleware.HeaderMiddleware{}) // 为当前节点添加上中间件\n\n    node.Get(\"/\", func(request *http.Request, p http.Response) http.ResponseData {\n        return http.NewRespDataHtml(404, \"blank\")\n    })\n\n    node.Controller(\"index\", &controller.IndexController{})\n}\n"
    // IndexController
    files["app/controller/IndexController.go"] = "package controller\n\nimport (\n    \"fmt\"\n    \"strings\"\n    \"github.com/bysir-zl/bygo/http\"\n)\n\ntype IndexController struct{}\n\nfunc (p IndexController) Index(r *http.Request, s *http.Response) http.ResponseData {\n\n    return http.NewRespDataHtml(200, \"welcome to use bygo!\" + \"

\" +\n        \"Url: \" + r.Router.Url + \"
\" +\n        \"Handler: \" + r.Router.Handler + \"
\" +\n        \"RouterParams : \" + strings.Join(r.Router.Params, \",\") + \"
\" +\n        \"Input : \" + fmt.Sprint(r.Input.All()) + \"
\" +\n        \"Header : \" + fmt.Sprint(r.Header) + \"
\" +\n        \"\")\n}"

    // HeaderMiddleware
    files["app/middleware/HeaderMiddleware.go"] = "package middleware\n\nimport (\n    \"github.com/bysir-zl/bygo/http\"\n)\n\ntype HeaderMiddleware struct {\n\n}\n\nfunc (p HeaderMiddleware) HandlerBefore(s http.SessionContainer) (needStop bool, data http.ResponseData) {\n    s.Request.Input.Set(\"ext\", \"from middleware\")\n    return false, http.NewRespDataHtml(0, \"\")\n}\n\nfunc (p HeaderMiddleware) HandlerAfter(s http.SessionContainer) (needStop bool, data http.ResponseData) {\n\n    response := s.Response\n    response.ResponseData.Body = response.ResponseData.Body + \"

 i am from middleware\"\n    response.AddHeader(\"Access-Control-Allow-Origin\", \"*\") // 添加上允许跨域\n    response.AddHeader(\"Access-Control-Allow-Headers\", \"X_TOKEN\") // 添加上允许的头,用来身份验证\n\n    return false, http.NewRespDataHtml(0, \"\")\n}\n"

    // HeaderMiddleware
    files["app/exception/Exception.go"] = "package exception\n\nimport (\n    \"github.com/bysir-zl/bygo/bean\"\n    \"github.com/bysir-zl/bygo/http\"\n)\n\n// 将报错的Exception处理成Response返回。在这里你可以判断e.Code统一处理错误,比如上报code==500的错误\nfunc Handler(c http.SessionContainer, e http.Exceptions) http.ResponseData {\n    return http.NewRespDataJson(200, bean.ApiData{Code:e.Code, Msg:e.Message})\n}\n"

    // UserModel
    files["app/model/UserModel.go"] = "package model\n\ntype UserModel struct {\n    Table    string  `db:\"user\" json:\"-\"`\n    Connect  string `db:\"default\" json:\"-\"`\n\n    Id       int64 `name:\"id\" pk:\"auto\" json:\"id\"`\n    Password string `name:\"password\" json:\"password,omitempty\"`\n    UserName string `name:\"username\" json:\"username\"`\n\n    CreateAt string `name:\"create_at\" auto:\"time,insert\" json:\"create_at\"`\n    UpdateAt string `name:\"update_at\" auto:\"time,update|insert\" json:\"update_at\"`\n\n    Token    string `json:\"token,omitempty\"`\n}\n    "

    // config - app
    files["config/app.go"] = "package config\n\nvar Debug = true\n"

    // config - chche
    files["config/chche.go"] = "package config\n\nvar CacheDriver = \"redis\"\n"
    // config - db
    files["config/db.go"] = "package config\n\nimport \"github.com/bysir-zl/bygo/db\"\n\nvar DbConfigs map[string]db.DbConfig = map[string]db.DbConfig{}\n\nfunc init() {\n    if !Debug {\n        DbConfigs[\"default\"] = db.DbConfig{\n            Driver:\"mysql\",\n            Host:\"localhost\",\n            Port:3306,\n            Name:\"password\",\n            User:\"root\",\n            Password:\"zhangliang\",\n        }\n    } else {\n        DbConfigs[\"default\"] = db.DbConfig{\n            Driver:\"mysql\",\n            Host:\"localhost\",\n            Port:3306,\n            Name:\"password\",\n            User:\"root\",\n            Password:\"\",\n        }\n    }\n}\n"
    // config - redis
    files["config/redis.go"] = "package config\n\nvar RedisHost = \"127.0.0.1:6379\"\n"

    // 写入文件
    for filename, content := range files {
        filename = name + "/src/" + filename
        var f *os.File
        var err error
        if checkFileIsExist(filename) {
            f, err = os.OpenFile(filename, os.O_RDWR, os.ModePerm)
            if err != nil {
                panic(err)
            }

        } else {
            f, err = os.Create(filename)
            if err != nil {
                panic(err)
            }
        }

        f.Write([]byte(content))
    }

    log.Print("create success")
}<|endoftext|>


<commit_before>package arn

import (
	"errors"
	"fmt"
	"sort"
	"sync"

	"github.com/aerogo/nano"
)

// AnimeList ...
type AnimeList struct {
	UserID string           `json:"userId"`
	Items  []*AnimeListItem `json:"items"`

	sync.Mutex
}

// Add adds an anime to the list if it hasn't been added yet.
func (list *AnimeList) Add(animeID string) error {
	if list.Contains(animeID) {
		return errors.New("Anime " + animeID + " has already been added")
	}

	creationDate := DateTimeUTC()

	item := &AnimeListItem{
		AnimeID: animeID,
		Status:  AnimeListStatusPlanned,
		Rating:  AnimeListItemRating{},
		Created: creationDate,
		Edited:  creationDate,
	}

	if item.Anime() == nil {
		return errors.New("Invalid anime ID")
	}

	list.Lock()
	list.Items = append(list.Items, item)
	list.Unlock()

	return nil
}

// Remove removes the anime ID from the list.
func (list *AnimeList) Remove(animeID string) bool {
	list.Lock()
	defer list.Unlock()

	for index, item := range list.Items {
		if item.AnimeID == animeID {
			list.Items = append(list.Items[:index], list.Items[index+1:]...)
			return true
		}
	}

	return false
}

// Contains checks if the list contains the anime ID already.
func (list *AnimeList) Contains(animeID string) bool {
	list.Lock()
	defer list.Unlock()

	for _, item := range list.Items {
		if item.AnimeID == animeID {
			return true
		}
	}

	return false
}

// Find returns the list item with the specified anime ID, if available.
func (list *AnimeList) Find(animeID string) *AnimeListItem {
	list.Lock()
	defer list.Unlock()

	for _, item := range list.Items {
		if item.AnimeID == animeID {
			return item
		}
	}

	return nil
}

// Import adds an anime to the list if it hasn't been added yet
// and if it did exist it will update episode, rating and notes.
func (list *AnimeList) Import(item *AnimeListItem) {
	existing := list.Find(item.AnimeID)

	// If it doesn't exist yet: Simply add it.
	if existing == nil {
		list.Lock()
		list.Items = append(list.Items, item)
		list.Unlock()
		return
	}

	// Temporary save it before changing the status
	// because status changes can modify the episode count.
	// This will prevent loss of "episodes watched" data.
	existingEpisodes := existing.Episodes

	// Status
	existing.Status = item.Status
	existing.OnStatusChange()

	// Episodes
	if item.Episodes > existingEpisodes {
		existing.Episodes = item.Episodes
	} else {
		existing.Episodes = existingEpisodes
	}

	existing.OnEpisodesChange()

	// Rating
	if existing.Rating.Overall == 0 {
		existing.Rating.Overall = item.Rating.Overall
		existing.Rating.Clamp()
	}

	if existing.Notes == "" {
		existing.Notes = item.Notes
	}

	if item.RewatchCount > existing.RewatchCount {
		existing.RewatchCount = item.RewatchCount
	}

	// Edited
	existing.Edited = DateTimeUTC()
}

// User returns the user this anime list belongs to.
func (list *AnimeList) User() *User {
	user, _ := GetUser(list.UserID)
	return user
}

// Sort ...
func (list *AnimeList) Sort() {
	list.Lock()
	defer list.Unlock()

	sort.Slice(list.Items, func(i, j int) bool {
		a := list.Items[i]
		b := list.Items[j]

		if (a.Status != AnimeListStatusWatching && a.Status != AnimeListStatusPlanned) && (b.Status != AnimeListStatusWatching && b.Status != AnimeListStatusPlanned) {
			if a.Rating.Overall == b.Rating.Overall {
				return a.Anime().Title.Canonical < b.Anime().Title.Canonical
			}

			return a.Rating.Overall > b.Rating.Overall
		}

		epsA := a.Anime().UpcomingEpisode()
		epsB := b.Anime().UpcomingEpisode()

		if epsA == nil && epsB == nil {
			if a.Rating.Overall == b.Rating.Overall {
				return a.Anime().Title.Canonical < b.Anime().Title.Canonical
			}

			return a.Rating.Overall > b.Rating.Overall
		}

		if epsA == nil {
			return false
		}

		if epsB == nil {
			return true
		}

		return epsA.Episode.AiringDate.Start < epsB.Episode.AiringDate.Start
	})
}

// SortByRating sorts the anime list by overall rating.
func (list *AnimeList) SortByRating() {
	list.Lock()
	defer list.Unlock()

	sort.Slice(list.Items, func(i, j int) bool {
		a := list.Items[i]
		b := list.Items[j]

		if a.Rating.Overall == b.Rating.Overall {
			return a.Anime().Title.Canonical < b.Anime().Title.Canonical
		}

		return a.Rating.Overall > b.Rating.Overall
	})
}

// Watching ...
func (list *AnimeList) Watching() *AnimeList {
	return list.FilterStatus(AnimeListStatusWatching)
}

// FilterStatus ...
func (list *AnimeList) FilterStatus(status string) *AnimeList {
	newList := &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	list.Lock()
	defer list.Unlock()

	for _, item := range list.Items {
		if item.Status == status { // (item.Status == AnimeListStatusPlanned)
			newList.Items = append(newList.Items, item)
		}
	}

	return newList
}

// SplitByStatus splits the anime list into multiple ones by status.
func (list *AnimeList) SplitByStatus() map[string]*AnimeList {
	statusToList := map[string]*AnimeList{}

	statusToList[AnimeListStatusWatching] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	statusToList[AnimeListStatusCompleted] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	statusToList[AnimeListStatusPlanned] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	statusToList[AnimeListStatusHold] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	statusToList[AnimeListStatusDropped] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	list.Lock()
	defer list.Unlock()

	for _, item := range list.Items {
		statusList := statusToList[item.Status]
		statusList.Items = append(statusList.Items, item)
	}

	return statusToList
}

// NormalizeRatings normalizes all ratings so that they are perfectly stretched among the full scale.
func (list *AnimeList) NormalizeRatings() {
	list.Lock()
	defer list.Unlock()

	mapped := map[float64]float64{}
	all := []float64{}

	for _, item := range list.Items {
		// Zero rating counts as not rated
		if item.Rating.Overall == 0 {
			continue
		}

		_, found := mapped[item.Rating.Overall]

		if !found {
			mapped[item.Rating.Overall] = item.Rating.Overall
			all = append(all, item.Rating.Overall)
		}
	}

	sort.Slice(all, func(i, j int) bool {
		return all[i] < all[j]
	})

	count := len(all)

	// Prevent division by zero
	if count <= 1 {
		return
	}

	step := 9.9 / float64(count-1)
	currentRating := 0.1

	for _, rating := range all {
		mapped[rating] = currentRating
		currentRating += step
	}

	for _, item := range list.Items {
		item.Rating.Overall = mapped[item.Rating.Overall]
		item.Rating.Clamp()
	}
}

// Genres returns a map of genre names mapped to the list items that belong to that genre.
func (list *AnimeList) Genres() map[string][]*AnimeListItem {
	genreToListItems := map[string][]*AnimeListItem{}

	for _, item := range list.Items {
		for _, genre := range item.Anime().Genres {
			genreToListItems[genre] = append(genreToListItems[genre], item)
		}
	}

	return genreToListItems
}

// RemoveDuplicates removes duplicate entries.
func (list *AnimeList) RemoveDuplicates() {
	list.Lock()
	defer list.Unlock()

	existed := map[string]bool{}
	newItems := make([]*AnimeListItem, 0, len(list.Items))

	for _, item := range list.Items {
		_, exists := existed[item.AnimeID]

		if exists {
			fmt.Println(list.User().Nick, "removed anime list item duplicate", item.AnimeID)
			continue
		}

		newItems = append(newItems, item)
		existed[item.AnimeID] = true
	}

	list.Items = newItems
}

// StreamAnimeLists returns a stream of all anime.
func StreamAnimeLists() chan *AnimeList {
	channel := make(chan *AnimeList, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("AnimeList") {
			channel <- obj.(*AnimeList)
		}

		close(channel)
	}()

	return channel
}

// AllAnimeLists returns a slice of all anime.
func AllAnimeLists() ([]*AnimeList, error) {
	var all []*AnimeList

	stream := StreamAnimeLists()

	for obj := range stream {
		all = append(all, obj)
	}

	return all, nil
}

// GetAnimeList ...
func GetAnimeList(userID string) (*AnimeList, error) {
	animeList, err := DB.Get("AnimeList", userID)

	if err != nil {
		return nil, err
	}

	return animeList.(*AnimeList), nil
}
<commit_msg>Minor change<commit_after>package arn

import (
	"errors"
	"fmt"
	"sort"
	"sync"

	"github.com/aerogo/nano"
)

// AnimeList ...
type AnimeList struct {
	UserID string           `json:"userId"`
	Items  []*AnimeListItem `json:"items"`

	sync.Mutex
}

// Add adds an anime to the list if it hasn't been added yet.
func (list *AnimeList) Add(animeID string) error {
	if list.Contains(animeID) {
		return errors.New("Anime " + animeID + " has already been added")
	}

	creationDate := DateTimeUTC()

	item := &AnimeListItem{
		AnimeID: animeID,
		Status:  AnimeListStatusPlanned,
		Rating:  AnimeListItemRating{},
		Created: creationDate,
		Edited:  creationDate,
	}

	if item.Anime() == nil {
		return errors.New("Invalid anime ID")
	}

	list.Lock()
	list.Items = append(list.Items, item)
	list.Unlock()

	return nil
}

// Remove removes the anime ID from the list.
func (list *AnimeList) Remove(animeID string) bool {
	list.Lock()
	defer list.Unlock()

	for index, item := range list.Items {
		if item.AnimeID == animeID {
			list.Items = append(list.Items[:index], list.Items[index+1:]...)
			return true
		}
	}

	return false
}

// Contains checks if the list contains the anime ID already.
func (list *AnimeList) Contains(animeID string) bool {
	list.Lock()
	defer list.Unlock()

	for _, item := range list.Items {
		if item.AnimeID == animeID {
			return true
		}
	}

	return false
}

// Find returns the list item with the specified anime ID, if available.
func (list *AnimeList) Find(animeID string) *AnimeListItem {
	list.Lock()
	defer list.Unlock()

	for _, item := range list.Items {
		if item.AnimeID == animeID {
			return item
		}
	}

	return nil
}

// Import adds an anime to the list if it hasn't been added yet
// and if it did exist it will update episode, rating and notes.
func (list *AnimeList) Import(item *AnimeListItem) {
	existing := list.Find(item.AnimeID)

	// If it doesn't exist yet: Simply add it.
	if existing == nil {
		list.Lock()
		list.Items = append(list.Items, item)
		list.Unlock()
		return
	}

	// Temporary save it before changing the status
	// because status changes can modify the episode count.
	// This will prevent loss of "episodes watched" data.
	existingEpisodes := existing.Episodes

	// Status
	existing.Status = item.Status
	existing.OnStatusChange()

	// Episodes
	if item.Episodes > existingEpisodes {
		existing.Episodes = item.Episodes
	} else {
		existing.Episodes = existingEpisodes
	}

	existing.OnEpisodesChange()

	// Rating
	if existing.Rating.Overall == 0 {
		existing.Rating.Overall = item.Rating.Overall
		existing.Rating.Clamp()
	}

	if existing.Notes == "" {
		existing.Notes = item.Notes
	}

	if item.RewatchCount > existing.RewatchCount {
		existing.RewatchCount = item.RewatchCount
	}

	// Edited
	existing.Edited = DateTimeUTC()
}

// User returns the user this anime list belongs to.
func (list *AnimeList) User() *User {
	user, _ := GetUser(list.UserID)
	return user
}

// Sort ...
func (list *AnimeList) Sort() {
	list.Lock()
	defer list.Unlock()

	sort.Slice(list.Items, func(i, j int) bool {
		a := list.Items[i]
		b := list.Items[j]

		if (a.Status != AnimeListStatusWatching && a.Status != AnimeListStatusPlanned) && (b.Status != AnimeListStatusWatching && b.Status != AnimeListStatusPlanned) {
			if a.Rating.Overall == b.Rating.Overall {
				return a.Anime().Title.Canonical < b.Anime().Title.Canonical
			}

			return a.Rating.Overall > b.Rating.Overall
		}

		epsA := a.Anime().UpcomingEpisode()
		epsB := b.Anime().UpcomingEpisode()

		if epsA == nil && epsB == nil {
			if a.Rating.Overall == b.Rating.Overall {
				return a.Anime().Title.Canonical < b.Anime().Title.Canonical
			}

			return a.Rating.Overall > b.Rating.Overall
		}

		if epsA == nil {
			return false
		}

		if epsB == nil {
			return true
		}

		return epsA.Episode.AiringDate.Start < epsB.Episode.AiringDate.Start
	})
}

// SortByRating sorts the anime list by overall rating.
func (list *AnimeList) SortByRating() {
	list.Lock()
	defer list.Unlock()

	sort.Slice(list.Items, func(i, j int) bool {
		a := list.Items[i]
		b := list.Items[j]

		if a.Rating.Overall == b.Rating.Overall {
			return a.Anime().Title.Canonical < b.Anime().Title.Canonical
		}

		return a.Rating.Overall > b.Rating.Overall
	})
}

// Watching ...
func (list *AnimeList) Watching() *AnimeList {
	return list.FilterStatus(AnimeListStatusWatching)
}

// FilterStatus ...
func (list *AnimeList) FilterStatus(status string) *AnimeList {
	newList := &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	list.Lock()
	defer list.Unlock()

	for _, item := range list.Items {
		if item.Status == status {
			newList.Items = append(newList.Items, item)
		}
	}

	return newList
}

// SplitByStatus splits the anime list into multiple ones by status.
func (list *AnimeList) SplitByStatus() map[string]*AnimeList {
	statusToList := map[string]*AnimeList{}

	statusToList[AnimeListStatusWatching] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	statusToList[AnimeListStatusCompleted] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	statusToList[AnimeListStatusPlanned] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	statusToList[AnimeListStatusHold] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	statusToList[AnimeListStatusDropped] = &AnimeList{
		UserID: list.UserID,
		Items:  []*AnimeListItem{},
	}

	list.Lock()
	defer list.Unlock()

	for _, item := range list.Items {
		statusList := statusToList[item.Status]
		statusList.Items = append(statusList.Items, item)
	}

	return statusToList
}

// NormalizeRatings normalizes all ratings so that they are perfectly stretched among the full scale.
func (list *AnimeList) NormalizeRatings() {
	list.Lock()
	defer list.Unlock()

	mapped := map[float64]float64{}
	all := []float64{}

	for _, item := range list.Items {
		// Zero rating counts as not rated
		if item.Rating.Overall == 0 {
			continue
		}

		_, found := mapped[item.Rating.Overall]

		if !found {
			mapped[item.Rating.Overall] = item.Rating.Overall
			all = append(all, item.Rating.Overall)
		}
	}

	sort.Slice(all, func(i, j int) bool {
		return all[i] < all[j]
	})

	count := len(all)

	// Prevent division by zero
	if count <= 1 {
		return
	}

	step := 9.9 / float64(count-1)
	currentRating := 0.1

	for _, rating := range all {
		mapped[rating] = currentRating
		currentRating += step
	}

	for _, item := range list.Items {
		item.Rating.Overall = mapped[item.Rating.Overall]
		item.Rating.Clamp()
	}
}

// Genres returns a map of genre names mapped to the list items that belong to that genre.
func (list *AnimeList) Genres() map[string][]*AnimeListItem {
	genreToListItems := map[string][]*AnimeListItem{}

	for _, item := range list.Items {
		for _, genre := range item.Anime().Genres {
			genreToListItems[genre] = append(genreToListItems[genre], item)
		}
	}

	return genreToListItems
}

// RemoveDuplicates removes duplicate entries.
func (list *AnimeList) RemoveDuplicates() {
	list.Lock()
	defer list.Unlock()

	existed := map[string]bool{}
	newItems := make([]*AnimeListItem, 0, len(list.Items))

	for _, item := range list.Items {
		_, exists := existed[item.AnimeID]

		if exists {
			fmt.Println(list.User().Nick, "removed anime list item duplicate", item.AnimeID)
			continue
		}

		newItems = append(newItems, item)
		existed[item.AnimeID] = true
	}

	list.Items = newItems
}

// StreamAnimeLists returns a stream of all anime.
func StreamAnimeLists() chan *AnimeList {
	channel := make(chan *AnimeList, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("AnimeList") {
			channel <- obj.(*AnimeList)
		}

		close(channel)
	}()

	return channel
}

// AllAnimeLists returns a slice of all anime.
func AllAnimeLists() ([]*AnimeList, error) {
	var all []*AnimeList

	stream := StreamAnimeLists()

	for obj := range stream {
		all = append(all, obj)
	}

	return all, nil
}

// GetAnimeList ...
func GetAnimeList(userID string) (*AnimeList, error) {
	animeList, err := DB.Get("AnimeList", userID)

	if err != nil {
		return nil, err
	}

	return animeList.(*AnimeList), nil
}
<|endoftext|>


<commit_before>package virtual_guest_lifecycle_test

import (
	"fmt"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	datatypes "github.com/maximilien/softlayer-go/data_types"
	softlayer "github.com/maximilien/softlayer-go/softlayer"
	testhelpers "github.com/maximilien/softlayer-go/test_helpers"
)

var _ = Describe("SoftLayer Virtual Guest Lifecycle", func() {
	var (
		err error

		accountService      softlayer.SoftLayer_Account_Service
		virtualGuestService softlayer.SoftLayer_Virtual_Guest_Service
	)

	BeforeEach(func() {
		accountService, err = testhelpers.CreateAccountService()
		Expect(err).ToNot(HaveOccurred())

		virtualGuestService, err = testhelpers.CreateVirtualGuestService()
		Expect(err).ToNot(HaveOccurred())

		testhelpers.TIMEOUT = 35 * time.Minute
		testhelpers.POLLING_INTERVAL = 10 * time.Second
	})

	Context("SoftLayer_Account#<getSshKeys, getVirtualGuests>", func() {
		It("returns an array of SoftLayer_Virtual_Guest objects", func() {
			virtualGuests, err := accountService.GetVirtualGuests()
			Expect(err).ToNot(HaveOccurred())
			Expect(len(virtualGuests)).To(BeNumerically(">=", 0))
		})

		It("returns an array of SoftLayer_Security_Ssh_Keys objects", func() {
			sshKeys, err := accountService.GetSshKeys()
			Expect(err).ToNot(HaveOccurred())
			Expect(len(sshKeys)).To(BeNumerically(">=", 0))
		})
	})

	Context("SoftLayer_SecuritySshKey#CreateObject and SoftLayer_SecuritySshKey#DeleteObject", func() {
		It("creates the ssh key and verify it is present and then deletes it", func() {
			createdSshKey, _ := testhelpers.CreateTestSshKey()
			testhelpers.WaitForCreatedSshKeyToBePresent(createdSshKey.Id)

			sshKeyService, err := testhelpers.CreateSecuritySshKeyService()
			Expect(err).ToNot(HaveOccurred())

			deleted, err := sshKeyService.DeleteObject(createdSshKey.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(deleted).To(BeTrue())

			testhelpers.WaitForDeletedSshKeyToNoLongerBePresent(createdSshKey.Id)
		})
	})

	Context("SoftLayer_VirtualGuest#CreateObject, SoftLayer_VirtualGuest#GetVirtualGuestPrimaryIpAddress, and SoftLayer_VirtualGuest#DeleteObject", func() {
		It("creates the virtual guest instance and waits for it to be active, get it's IP address, and then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			ipAddress := testhelpers.GetVirtualGuestPrimaryIpAddress(virtualGuest.Id)
			Expect(ipAddress).ToNot(Equal(""))
		})

		It("creates the virtual guest instance and waits for it to be active, get it's network VLANS, and then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)

			networkVlans, err := virtualGuestService.GetNetworkVlans(virtualGuest.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(networkVlans)).To(BeNumerically(">", 0))
		})

		It("creates the virtual guest and waits for it to be active and checks that the host could create a 1MB disk", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			virtualGuestService, err := testhelpers.CreateVirtualGuestService()
			Expect(err).ToNot(HaveOccurred())

			available, err := virtualGuestService.CheckHostDiskAvailability(virtualGuest.Id, 1024)
			Expect(err).ToNot(HaveOccurred())
			Expect(available).To(BeTrue())
		})
	})

	Context("SoftLayer_VirtualGuest#CreateObject, SoftLayer_VirtualGuest#rebootSoft, wait for reboot to complete, and SoftLayer_VirtualGuest#DeleteObject", func() {
		It("creates the virtual guest instance, wait for active, SOFT reboots it, wait for RUNNING, then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			virtualGuestService, err := testhelpers.CreateVirtualGuestService()
			Expect(err).ToNot(HaveOccurred())

			fmt.Printf("----> will attempt to SOFT reboot virtual guest `%d`\n", virtualGuest.Id)
			rebooted, err := virtualGuestService.RebootSoft(virtualGuest.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(rebooted).To(BeTrue())
			fmt.Printf("----> successfully SOFT rebooted virtual guest `%d`\n", virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
		})
	})

	Context("SoftLayer_VirtualGuest#CreateObject, SoftLayer_VirtualGuest#rebootHard, wait for reboot to complete, and SoftLayer_VirtualGuest#DeleteObject", func() {
		It("creates the virtual guest instance, wait for active, HARD reboots it, wait for RUNNING, then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			virtualGuestService, err := testhelpers.CreateVirtualGuestService()
			Expect(err).ToNot(HaveOccurred())

			fmt.Printf("----> will attempt to HARD reboot virtual guest `%d`\n", virtualGuest.Id)
			rebooted, err := virtualGuestService.RebootHard(virtualGuest.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(rebooted).To(BeTrue())
			fmt.Printf("----> successfully HARD rebooted virtual guest `%d`\n", virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
		})
	})

	Context("SoftLayer_SecuritySshKey#CreateObject and SoftLayer_VirtualGuest#CreateObject", func() {
		It("creates key, creates virtual guest and adds key to list of VG", func() {
			err = testhelpers.FindAndDeleteTestSshKeys()
			Expect(err).ToNot(HaveOccurred())

			createdSshKey, _ := testhelpers.CreateTestSshKey()
			testhelpers.WaitForCreatedSshKeyToBePresent(createdSshKey.Id)
			defer testhelpers.DeleteSshKey(createdSshKey.Id)

			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{createdSshKey})
			defer testhelpers.WaitForVirtualGuestToHaveNoActiveTransactionsOrToErr(virtualGuest.Id)
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
		})
	})

	Context("SoftLayer_VirtualGuest#CreateObject, SoftLayer_VirtualGuest#setTags, and SoftLayer_VirtualGuest#DeleteObject", func() {
		It("creates the virtual guest instance, wait for active, wait for RUNNING, set some tags, verify that tags are added, then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			virtualGuestService, err := testhelpers.CreateVirtualGuestService()
			Expect(err).ToNot(HaveOccurred())

			fmt.Printf("----> will attempt to set tags to the virtual guest `%d`\n", virtualGuest.Id)
			tags := []string{"tag0", "tag1", "tag2"}
			tagsWasSet, err := virtualGuestService.SetTags(virtualGuest.Id, tags)
			Expect(err).ToNot(HaveOccurred())
			Expect(tagsWasSet).To(BeTrue())

			fmt.Printf("----> verifying that tags were set the tags virtual guest `%d`\n", virtualGuest.Id)
			tagReferences, err := virtualGuestService.GetTagReferences(virtualGuest.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(tagReferences)).To(Equal(3))

			fmt.Printf("----> verify that each tag was set to virtual guest: `%d`\n", virtualGuest.Id)
			found := false
			for _, tag := range tags {
				for _, tagReference := range tagReferences {
					if tag == tagReference.Tag.Name {
						found = true
						break
					}
				}
				Expect(found).To(BeTrue())
				found = false
			}

			fmt.Printf("----> successfully set the tags and verified tags were set in virtual guest `%d`\n", virtualGuest.Id)
		})
	})
})
<commit_msg>Removed call to FindAndDeleteTeshSshKeys<commit_after>package virtual_guest_lifecycle_test

import (
	"fmt"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	datatypes "github.com/maximilien/softlayer-go/data_types"
	softlayer "github.com/maximilien/softlayer-go/softlayer"
	testhelpers "github.com/maximilien/softlayer-go/test_helpers"
)

var _ = Describe("SoftLayer Virtual Guest Lifecycle", func() {
	var (
		err error

		accountService      softlayer.SoftLayer_Account_Service
		virtualGuestService softlayer.SoftLayer_Virtual_Guest_Service
	)

	BeforeEach(func() {
		accountService, err = testhelpers.CreateAccountService()
		Expect(err).ToNot(HaveOccurred())

		virtualGuestService, err = testhelpers.CreateVirtualGuestService()
		Expect(err).ToNot(HaveOccurred())

		testhelpers.TIMEOUT = 35 * time.Minute
		testhelpers.POLLING_INTERVAL = 10 * time.Second
	})

	Context("SoftLayer_Account#<getSshKeys, getVirtualGuests>", func() {
		It("returns an array of SoftLayer_Virtual_Guest objects", func() {
			virtualGuests, err := accountService.GetVirtualGuests()
			Expect(err).ToNot(HaveOccurred())
			Expect(len(virtualGuests)).To(BeNumerically(">=", 0))
		})

		It("returns an array of SoftLayer_Security_Ssh_Keys objects", func() {
			sshKeys, err := accountService.GetSshKeys()
			Expect(err).ToNot(HaveOccurred())
			Expect(len(sshKeys)).To(BeNumerically(">=", 0))
		})
	})

	Context("SoftLayer_SecuritySshKey#CreateObject and SoftLayer_SecuritySshKey#DeleteObject", func() {
		It("creates the ssh key and verify it is present and then deletes it", func() {
			createdSshKey, _ := testhelpers.CreateTestSshKey()
			testhelpers.WaitForCreatedSshKeyToBePresent(createdSshKey.Id)

			sshKeyService, err := testhelpers.CreateSecuritySshKeyService()
			Expect(err).ToNot(HaveOccurred())

			deleted, err := sshKeyService.DeleteObject(createdSshKey.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(deleted).To(BeTrue())

			testhelpers.WaitForDeletedSshKeyToNoLongerBePresent(createdSshKey.Id)
		})
	})

	Context("SoftLayer_VirtualGuest#CreateObject, SoftLayer_VirtualGuest#GetVirtualGuestPrimaryIpAddress, and SoftLayer_VirtualGuest#DeleteObject", func() {
		It("creates the virtual guest instance and waits for it to be active, get it's IP address, and then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			ipAddress := testhelpers.GetVirtualGuestPrimaryIpAddress(virtualGuest.Id)
			Expect(ipAddress).ToNot(Equal(""))
		})

		It("creates the virtual guest instance and waits for it to be active, get it's network VLANS, and then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)

			networkVlans, err := virtualGuestService.GetNetworkVlans(virtualGuest.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(networkVlans)).To(BeNumerically(">", 0))
		})

		It("creates the virtual guest and waits for it to be active and checks that the host could create a 1MB disk", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			virtualGuestService, err := testhelpers.CreateVirtualGuestService()
			Expect(err).ToNot(HaveOccurred())

			available, err := virtualGuestService.CheckHostDiskAvailability(virtualGuest.Id, 1024)
			Expect(err).ToNot(HaveOccurred())
			Expect(available).To(BeTrue())
		})
	})

	Context("SoftLayer_VirtualGuest#CreateObject, SoftLayer_VirtualGuest#rebootSoft, wait for reboot to complete, and SoftLayer_VirtualGuest#DeleteObject", func() {
		It("creates the virtual guest instance, wait for active, SOFT reboots it, wait for RUNNING, then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			virtualGuestService, err := testhelpers.CreateVirtualGuestService()
			Expect(err).ToNot(HaveOccurred())

			fmt.Printf("----> will attempt to SOFT reboot virtual guest `%d`\n", virtualGuest.Id)
			rebooted, err := virtualGuestService.RebootSoft(virtualGuest.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(rebooted).To(BeTrue())
			fmt.Printf("----> successfully SOFT rebooted virtual guest `%d`\n", virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
		})
	})

	Context("SoftLayer_VirtualGuest#CreateObject, SoftLayer_VirtualGuest#rebootHard, wait for reboot to complete, and SoftLayer_VirtualGuest#DeleteObject", func() {
		It("creates the virtual guest instance, wait for active, HARD reboots it, wait for RUNNING, then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			virtualGuestService, err := testhelpers.CreateVirtualGuestService()
			Expect(err).ToNot(HaveOccurred())

			fmt.Printf("----> will attempt to HARD reboot virtual guest `%d`\n", virtualGuest.Id)
			rebooted, err := virtualGuestService.RebootHard(virtualGuest.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(rebooted).To(BeTrue())
			fmt.Printf("----> successfully HARD rebooted virtual guest `%d`\n", virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
		})
	})

	Context("SoftLayer_SecuritySshKey#CreateObject and SoftLayer_VirtualGuest#CreateObject", func() {
		It("creates key, creates virtual guest and adds key to list of VG", func() {
			createdSshKey, _ := testhelpers.CreateTestSshKey()
			testhelpers.WaitForCreatedSshKeyToBePresent(createdSshKey.Id)
			defer testhelpers.DeleteSshKey(createdSshKey.Id)

			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{createdSshKey})
			defer testhelpers.WaitForVirtualGuestToHaveNoActiveTransactionsOrToErr(virtualGuest.Id)
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
		})
	})

	Context("SoftLayer_VirtualGuest#CreateObject, SoftLayer_VirtualGuest#setTags, and SoftLayer_VirtualGuest#DeleteObject", func() {
		It("creates the virtual guest instance, wait for active, wait for RUNNING, set some tags, verify that tags are added, then delete it", func() {
			virtualGuest := testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{})
			defer testhelpers.CleanUpVirtualGuest(virtualGuest.Id)

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			virtualGuestService, err := testhelpers.CreateVirtualGuestService()
			Expect(err).ToNot(HaveOccurred())

			fmt.Printf("----> will attempt to set tags to the virtual guest `%d`\n", virtualGuest.Id)
			tags := []string{"tag0", "tag1", "tag2"}
			tagsWasSet, err := virtualGuestService.SetTags(virtualGuest.Id, tags)
			Expect(err).ToNot(HaveOccurred())
			Expect(tagsWasSet).To(BeTrue())

			fmt.Printf("----> verifying that tags were set the tags virtual guest `%d`\n", virtualGuest.Id)
			tagReferences, err := virtualGuestService.GetTagReferences(virtualGuest.Id)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(tagReferences)).To(Equal(3))

			fmt.Printf("----> verify that each tag was set to virtual guest: `%d`\n", virtualGuest.Id)
			found := false
			for _, tag := range tags {
				for _, tagReference := range tagReferences {
					if tag == tagReference.Tag.Name {
						found = true
						break
					}
				}
				Expect(found).To(BeTrue())
				found = false
			}

			fmt.Printf("----> successfully set the tags and verified tags were set in virtual guest `%d`\n", virtualGuest.Id)
		})
	})
})
<|endoftext|>


<commit_before>// Copyright (c) 2016 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
// this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, version 2.0.

package ui

import (
	"fmt"
	"github.com/richardwilkes/geom"
	"github.com/richardwilkes/ui/event"
	"syscall"
	"unsafe"
)

// #cgo linux LDFLAGS: -lX11 -lcairo
// #include <X11/Xlib.h>
// #include <X11/keysym.h>
// #include <X11/Xutil.h>
// #include <cairo/cairo.h>
// #include <cairo/cairo-xlib.h>
import "C"

var (
	running             bool
	awaitingQuit        bool
	xWindowCount        int
	xDisplay            *C.Display
	wmProtocolsAtom     C.Atom
	wmDeleteAtom        C.Atom
	goTaskAtom          C.Atom
	quitting            bool
	lastMouseDownWindow platformWindow
	lastMouseDownButton = -1
)

func platformStartUserInterface() {
	C.XInitThreads()
	if xDisplay = C.XOpenDisplay(nil); xDisplay == nil {
		panic("Failed to open the X11 display")
	}
	wmProtocolsAtom = C.XInternAtom(xDisplay, C.CString("WM_PROTOCOLS"), C.False)
	wmDeleteAtom = C.XInternAtom(xDisplay, C.CString("WM_DELETE_WINDOW"), C.False)
	goTaskAtom = C.XInternAtom(xDisplay, C.CString("GoTask"), C.False)
	running = true
	appWillFinishStartup()
	appDidFinishStartup()
	if xWindowCount == 0 && appShouldQuitAfterLastWindowClosed() {
		platformAttemptQuit()
	}
	for running {
		var event C.XEvent
		C.XNextEvent(xDisplay, &event)
		processOneEvent(&event)
	}
}

func processOneEvent(evt *C.XEvent) {
	anyEvent := (*C.XAnyEvent)(unsafe.Pointer(evt))
	window := platformWindow(uintptr(anyEvent.window))
	switch anyEvent._type {
	case C.KeyPress:
		processKeyEvent(evt, window, platformKeyDown)
	case C.KeyRelease:
		processKeyEvent(evt, window, platformKeyUp)
	case C.ButtonPress:
		buttonEvent := (*C.XButtonEvent)(unsafe.Pointer(evt))
		if isScrollWheelButton(buttonEvent.button) {
			var dx, dy float64
			switch buttonEvent.button {
			case 4: // Up
				dy = -1
			case 5: // Down
				dy = 1
			case 6: // Left
				dx = -1
			case 7: // Right
				dx = 1
			}
			handleWindowMouseWheelEvent(window, platformMouseWheel, convertKeyMask(buttonEvent.state), float64(buttonEvent.x), float64(buttonEvent.y), dx, dy)
		} else {
			lastMouseDownButton = getButton(buttonEvent.button)
			lastMouseDownWindow = window
			// RAW: Needs concept of click count
			handleWindowMouseEvent(window, platformMouseDown, convertKeyMask(buttonEvent.state), lastMouseDownButton, 0, float64(buttonEvent.x), float64(buttonEvent.y))
		}
	case C.ButtonRelease:
		buttonEvent := (*C.XButtonEvent)(unsafe.Pointer(evt))
		if !isScrollWheelButton(buttonEvent.button) {
			lastMouseDownButton = -1
			// RAW: Needs concept of click count
			handleWindowMouseEvent(window, platformMouseUp, convertKeyMask(buttonEvent.state), getButton(buttonEvent.button), 0, float64(buttonEvent.x), float64(buttonEvent.y))
		}
	case C.MotionNotify:
		motionEvent := (*C.XMotionEvent)(unsafe.Pointer(evt))
		if lastMouseDownButton != -1 {
			if window != lastMouseDownWindow {
				// RAW: Translate coordinates appropriately
				fmt.Println("need translation for mouse drag")
			}
			handleWindowMouseEvent(lastMouseDownWindow, platformMouseDragged, convertKeyMask(motionEvent.state), lastMouseDownButton, 0, float64(motionEvent.x), float64(motionEvent.y))
		} else {
			handleWindowMouseEvent(window, platformMouseMoved, convertKeyMask(motionEvent.state), 0, 0, float64(motionEvent.x), float64(motionEvent.y))
		}
	case C.EnterNotify:
		crossingEvent := (*C.XCrossingEvent)(unsafe.Pointer(evt))
		handleWindowMouseEvent(window, platformMouseEntered, convertKeyMask(crossingEvent.state), 0, 0, float64(crossingEvent.x), float64(crossingEvent.y))
	case C.LeaveNotify:
		crossingEvent := (*C.XCrossingEvent)(unsafe.Pointer(evt))
		handleWindowMouseEvent(window, platformMouseExited, convertKeyMask(crossingEvent.state), 0, 0, float64(crossingEvent.x), float64(crossingEvent.y))
	case C.FocusIn:
		appWillBecomeActive()
		appDidBecomeActive()
	case C.FocusOut:
		appWillResignActive()
		appDidResignActive()
	case C.Expose:
		if win, ok := windowMap[window]; ok {
			exposeEvent := (*C.XExposeEvent)(unsafe.Pointer(evt))
			gc := C.cairo_create(win.surface)
			C.cairo_set_line_width(gc, 1)
			C.cairo_rectangle(gc, C.double(exposeEvent.x), C.double(exposeEvent.y), C.double(exposeEvent.width), C.double(exposeEvent.height))
			C.cairo_clip(gc)
			drawWindow(window, gc, platformRect{x: C.double(exposeEvent.x), y: C.double(exposeEvent.y), width: C.double(exposeEvent.width), height: C.double(exposeEvent.height)}, false)
			C.cairo_destroy(gc)
		}
	case C.DestroyNotify:
		windowDidClose(window)
		if xWindowCount == 0 {
			if quitting {
				finishQuit()
			}
			if appShouldQuitAfterLastWindowClosed() {
				platformAttemptQuit()
			}
		}
	case C.ConfigureNotify:
		var other C.XEvent
		for C.XCheckTypedWindowEvent(xDisplay, anyEvent.window, C.ConfigureNotify, &other) != 0 {
			// Collect up the last resize event for this window that is already in the queue and use that one instead
			evt = &other
		}
		if win, ok := windowMap[window]; ok {
			win.ignoreRepaint = true
			configEvent := (*C.XConfigureEvent)(unsafe.Pointer(evt))
			lastKnownWindowBounds[window] = geom.Rect{Point: geom.Point{X: float64(configEvent.x), Y: float64(configEvent.y)}, Size: geom.Size{Width: float64(configEvent.width), Height: float64(configEvent.height)}}
			windowResized(window)
			win.root.ValidateLayout()
			win.ignoreRepaint = false
			size := win.ContentFrame().Size
			C.cairo_xlib_surface_set_size(win.surface, C.int(size.Width), C.int(size.Height))
		}
	case C.ClientMessage:
		clientEvent := (*C.XClientMessageEvent)(unsafe.Pointer(evt))
		switch clientEvent.message_type {
		case wmProtocolsAtom:
			if clientEvent.format == 32 {
				data := (*C.Atom)(unsafe.Pointer(&clientEvent.data))
				if *data == wmDeleteAtom {
					if windowShouldClose(window) {
						if win, ok := windowMap[window]; ok {
							win.Close()
						}
					}
				}
			}
		case goTaskAtom:
			data := (*uint64)(unsafe.Pointer(&clientEvent.data))
			dispatchTask(*data)
		}
	}
}

func processKeyEvent(evt *C.XEvent, window platformWindow, eventType platformEventType) {
	keyEvent := (*C.XKeyEvent)(unsafe.Pointer(evt))
	var buffer [5]C.char
	var keySym C.KeySym
	buffer[C.XLookupString(keyEvent, &buffer[0], C.int(len(buffer)-1), &keySym, nil)] = 0
	handleWindowKeyEvent(window, eventType, convertKeyMask(keyEvent.state), int(keySym), &buffer[0], false)
}

func paintWindow(pWindow platformWindow, gc *C.cairo_t, x, y, width, height C.double, future bool) {
	C.cairo_save(gc)
	C.cairo_rectangle(gc, x, y, width, height)
	C.cairo_clip(gc)
	drawWindow(pWindow, gc, platformRect{x: C.double(x), y: C.double(y), width: C.double(width), height: C.double(height)}, false)
	C.cairo_restore(gc)
}

func platformAppName() string {
	// RAW: Implement platformAppName for Linux
	return "<unknown>"
}

func platformHideApp() {
	// RAW: Implement for Linux
}

func platformHideOtherApps() {
	// RAW: Implement for Linux
}

func platformShowAllApps() {
	// RAW: Implement for Linux
}

func platformAttemptQuit() {
	switch appShouldQuit() {
	case QuitCancel:
	case QuitLater:
		awaitingQuit = true
	default:
		initiateQuit()
	}
}

func platformAppMayQuitNow(quit bool) {
	if awaitingQuit {
		awaitingQuit = false
		if quit {
			initiateQuit()
		}
	}
}

func initiateQuit() {
	appWillQuit()
	quitting = true
	if xWindowCount > 0 {
		for _, w := range Windows() {
			w.Close()
		}
	} else {
		finishQuit()
	}
}

func finishQuit() {
	running = false
	C.XCloseDisplay(xDisplay)
	xDisplay = nil
	syscall.Exit(0)
}

func isScrollWheelButton(button C.uint) bool {
	return button > 3 && button < 8
}

func getButton(button C.uint) int {
	if button == 2 {
		return 2
	}
	if button == 3 {
		return 1
	}
	return 0
}

func convertKeyMask(state C.uint) int {
	var keyMask event.KeyMask
	if state&C.LockMask == C.LockMask {
		keyMask |= event.CapsLockKeyMask
	}
	if state&C.ShiftMask == C.ShiftMask {
		keyMask |= event.ShiftKeyMask
	}
	if state&C.ControlMask == C.ControlMask {
		keyMask |= event.ControlKeyMask
	}
	if state&C.Mod1Mask == C.Mod1Mask {
		keyMask |= event.OptionKeyMask
	}
	if state&C.Mod4Mask == C.Mod4Mask {
		keyMask |= event.CommandKeyMask
	}
	return int(keyMask)
}
<commit_msg>Implement multi-click support<commit_after>// Copyright (c) 2016 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
// this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, version 2.0.

package ui

import (
	"fmt"
	"github.com/richardwilkes/geom"
	"github.com/richardwilkes/ui/event"
	"math"
	"syscall"
	"time"
	"unsafe"
)

// #cgo linux LDFLAGS: -lX11 -lcairo
// #include <X11/Xlib.h>
// #include <X11/keysym.h>
// #include <X11/Xutil.h>
// #include <cairo/cairo.h>
// #include <cairo/cairo-xlib.h>
import "C"

var (
	running         bool
	quitting        bool
	awaitingQuit    bool
	xWindowCount    int
	xDisplay        *C.Display
	wmProtocolsAtom C.Atom
	wmDeleteAtom    C.Atom
	goTaskAtom      C.Atom
	// DoubleClickTime holds the maximum amount of time that can elapse between two clicks for them
	// to be considered part of a multi-click event.
	DoubleClickTime time.Duration = time.Millisecond * 250
	// DoubleClickDistance holds the maximum distance subsequent clicks can be from the last click
	// when determining if a click is part of a multi-click event.
	DoubleClickDistance float64 = 5
	clickCount          int
	lastClick           time.Time
	lastClickSpot       geom.Point
	lastClickButton     int = -1
	lastMouseDownWindow platformWindow
	lastMouseDownButton int = -1
)

func platformStartUserInterface() {
	C.XInitThreads()
	if xDisplay = C.XOpenDisplay(nil); xDisplay == nil {
		panic("Failed to open the X11 display")
	}
	wmProtocolsAtom = C.XInternAtom(xDisplay, C.CString("WM_PROTOCOLS"), C.False)
	wmDeleteAtom = C.XInternAtom(xDisplay, C.CString("WM_DELETE_WINDOW"), C.False)
	goTaskAtom = C.XInternAtom(xDisplay, C.CString("GoTask"), C.False)
	running = true
	appWillFinishStartup()
	appDidFinishStartup()
	if xWindowCount == 0 && appShouldQuitAfterLastWindowClosed() {
		platformAttemptQuit()
	}
	for running {
		var event C.XEvent
		C.XNextEvent(xDisplay, &event)
		processOneEvent(&event)
	}
}

func processOneEvent(evt *C.XEvent) {
	anyEvent := (*C.XAnyEvent)(unsafe.Pointer(evt))
	window := platformWindow(uintptr(anyEvent.window))
	switch anyEvent._type {
	case C.KeyPress:
		processKeyEvent(evt, window, platformKeyDown)
	case C.KeyRelease:
		processKeyEvent(evt, window, platformKeyUp)
	case C.ButtonPress:
		buttonEvent := (*C.XButtonEvent)(unsafe.Pointer(evt))
		if isScrollWheelButton(buttonEvent.button) {
			var dx, dy float64
			switch buttonEvent.button {
			case 4: // Up
				dy = -1
			case 5: // Down
				dy = 1
			case 6: // Left
				dx = -1
			case 7: // Right
				dx = 1
			}
			handleWindowMouseWheelEvent(window, platformMouseWheel, convertKeyMask(buttonEvent.state), float64(buttonEvent.x), float64(buttonEvent.y), dx, dy)
		} else {
			lastMouseDownButton = getButton(buttonEvent.button)
			lastMouseDownWindow = window
			x := float64(buttonEvent.x)
			y := float64(buttonEvent.y)
			now := time.Now()
			if lastClickButton == lastMouseDownButton && now.Sub(lastClick) <= DoubleClickTime && math.Abs(lastClickSpot.X-x) <= DoubleClickDistance && math.Abs(lastClickSpot.Y-y) <= DoubleClickDistance {
				clickCount++
			} else {
				clickCount = 1
			}
			lastClick = now
			lastClickButton = lastMouseDownButton
			lastClickSpot.X = x
			lastClickSpot.Y = y
			handleWindowMouseEvent(window, platformMouseDown, convertKeyMask(buttonEvent.state), lastMouseDownButton, clickCount, x, y)
		}
	case C.ButtonRelease:
		buttonEvent := (*C.XButtonEvent)(unsafe.Pointer(evt))
		if !isScrollWheelButton(buttonEvent.button) {
			lastMouseDownButton = -1
			handleWindowMouseEvent(window, platformMouseUp, convertKeyMask(buttonEvent.state), getButton(buttonEvent.button), clickCount, float64(buttonEvent.x), float64(buttonEvent.y))
		}
	case C.MotionNotify:
		motionEvent := (*C.XMotionEvent)(unsafe.Pointer(evt))
		if lastMouseDownButton != -1 {
			if window != lastMouseDownWindow {
				// RAW: Translate coordinates appropriately
				fmt.Println("need translation for mouse drag")
			}
			handleWindowMouseEvent(lastMouseDownWindow, platformMouseDragged, convertKeyMask(motionEvent.state), lastMouseDownButton, 0, float64(motionEvent.x), float64(motionEvent.y))
		} else {
			handleWindowMouseEvent(window, platformMouseMoved, convertKeyMask(motionEvent.state), 0, 0, float64(motionEvent.x), float64(motionEvent.y))
		}
	case C.EnterNotify:
		crossingEvent := (*C.XCrossingEvent)(unsafe.Pointer(evt))
		handleWindowMouseEvent(window, platformMouseEntered, convertKeyMask(crossingEvent.state), 0, 0, float64(crossingEvent.x), float64(crossingEvent.y))
	case C.LeaveNotify:
		crossingEvent := (*C.XCrossingEvent)(unsafe.Pointer(evt))
		handleWindowMouseEvent(window, platformMouseExited, convertKeyMask(crossingEvent.state), 0, 0, float64(crossingEvent.x), float64(crossingEvent.y))
	case C.FocusIn:
		appWillBecomeActive()
		appDidBecomeActive()
	case C.FocusOut:
		appWillResignActive()
		appDidResignActive()
	case C.Expose:
		if win, ok := windowMap[window]; ok {
			exposeEvent := (*C.XExposeEvent)(unsafe.Pointer(evt))
			gc := C.cairo_create(win.surface)
			C.cairo_set_line_width(gc, 1)
			C.cairo_rectangle(gc, C.double(exposeEvent.x), C.double(exposeEvent.y), C.double(exposeEvent.width), C.double(exposeEvent.height))
			C.cairo_clip(gc)
			drawWindow(window, gc, platformRect{x: C.double(exposeEvent.x), y: C.double(exposeEvent.y), width: C.double(exposeEvent.width), height: C.double(exposeEvent.height)}, false)
			C.cairo_destroy(gc)
		}
	case C.DestroyNotify:
		windowDidClose(window)
		if xWindowCount == 0 {
			if quitting {
				finishQuit()
			}
			if appShouldQuitAfterLastWindowClosed() {
				platformAttemptQuit()
			}
		}
	case C.ConfigureNotify:
		var other C.XEvent
		for C.XCheckTypedWindowEvent(xDisplay, anyEvent.window, C.ConfigureNotify, &other) != 0 {
			// Collect up the last resize event for this window that is already in the queue and use that one instead
			evt = &other
		}
		if win, ok := windowMap[window]; ok {
			win.ignoreRepaint = true
			configEvent := (*C.XConfigureEvent)(unsafe.Pointer(evt))
			lastKnownWindowBounds[window] = geom.Rect{Point: geom.Point{X: float64(configEvent.x), Y: float64(configEvent.y)}, Size: geom.Size{Width: float64(configEvent.width), Height: float64(configEvent.height)}}
			windowResized(window)
			win.root.ValidateLayout()
			win.ignoreRepaint = false
			size := win.ContentFrame().Size
			C.cairo_xlib_surface_set_size(win.surface, C.int(size.Width), C.int(size.Height))
		}
	case C.ClientMessage:
		clientEvent := (*C.XClientMessageEvent)(unsafe.Pointer(evt))
		switch clientEvent.message_type {
		case wmProtocolsAtom:
			if clientEvent.format == 32 {
				data := (*C.Atom)(unsafe.Pointer(&clientEvent.data))
				if *data == wmDeleteAtom {
					if windowShouldClose(window) {
						if win, ok := windowMap[window]; ok {
							win.Close()
						}
					}
				}
			}
		case goTaskAtom:
			data := (*uint64)(unsafe.Pointer(&clientEvent.data))
			dispatchTask(*data)
		}
	}
}

func processKeyEvent(evt *C.XEvent, window platformWindow, eventType platformEventType) {
	keyEvent := (*C.XKeyEvent)(unsafe.Pointer(evt))
	var buffer [5]C.char
	var keySym C.KeySym
	buffer[C.XLookupString(keyEvent, &buffer[0], C.int(len(buffer)-1), &keySym, nil)] = 0
	handleWindowKeyEvent(window, eventType, convertKeyMask(keyEvent.state), int(keySym), &buffer[0], false)
}

func paintWindow(pWindow platformWindow, gc *C.cairo_t, x, y, width, height C.double, future bool) {
	C.cairo_save(gc)
	C.cairo_rectangle(gc, x, y, width, height)
	C.cairo_clip(gc)
	drawWindow(pWindow, gc, platformRect{x: C.double(x), y: C.double(y), width: C.double(width), height: C.double(height)}, false)
	C.cairo_restore(gc)
}

func platformAppName() string {
	// RAW: Implement platformAppName for Linux
	return "<unknown>"
}

func platformHideApp() {
	// RAW: Implement for Linux
}

func platformHideOtherApps() {
	// RAW: Implement for Linux
}

func platformShowAllApps() {
	// RAW: Implement for Linux
}

func platformAttemptQuit() {
	switch appShouldQuit() {
	case QuitCancel:
	case QuitLater:
		awaitingQuit = true
	default:
		initiateQuit()
	}
}

func platformAppMayQuitNow(quit bool) {
	if awaitingQuit {
		awaitingQuit = false
		if quit {
			initiateQuit()
		}
	}
}

func initiateQuit() {
	appWillQuit()
	quitting = true
	if xWindowCount > 0 {
		for _, w := range Windows() {
			w.Close()
		}
	} else {
		finishQuit()
	}
}

func finishQuit() {
	running = false
	C.XCloseDisplay(xDisplay)
	xDisplay = nil
	syscall.Exit(0)
}

func isScrollWheelButton(button C.uint) bool {
	return button > 3 && button < 8
}

func getButton(button C.uint) int {
	if button == 2 {
		return 2
	}
	if button == 3 {
		return 1
	}
	return 0
}

func convertKeyMask(state C.uint) int {
	var keyMask event.KeyMask
	if state&C.LockMask == C.LockMask {
		keyMask |= event.CapsLockKeyMask
	}
	if state&C.ShiftMask == C.ShiftMask {
		keyMask |= event.ShiftKeyMask
	}
	if state&C.ControlMask == C.ControlMask {
		keyMask |= event.ControlKeyMask
	}
	if state&C.Mod1Mask == C.Mod1Mask {
		keyMask |= event.OptionKeyMask
	}
	if state&C.Mod4Mask == C.Mod4Mask {
		keyMask |= event.CommandKeyMask
	}
	return int(keyMask)
}
<|endoftext|>


<commit_before>package gb

import (
	"fmt"
	"strconv"
	"strings"
	"testing"

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

const maxIterations = 5000

func cpuInstTest(t *testing.T, options ...GameboyOption) {
	output := ""
	transferOption := WithTransferFunction(func(val byte) {
		output += string(val)
	})
	options = append(options, transferOption)
	gb, err := NewGameboy("./../roms/cpu_instrs.gb", options...)
	require.NoError(t, err, "error in init gb %v", err)

	// Expect the output to be 75 characters long
	expected := 106

	// Run the CPU until the output has matched the expected
	// or until maxIterations iterations have passed.
	for i := 0; i < maxIterations; i++ {
		gb.Update()
		if len(output) >= expected {
			break
		}
	}

	// Trim off the title and any whitespace
	trimmed := strings.TrimSpace(strings.TrimPrefix(output, "cpu_instrs"))
	require.True(t, len(trimmed) >= 94, "did not finish getting output in %v iterations: %v", maxIterations, trimmed)

	for i := int64(0); i < 11; i++ {
		t.Run(fmt.Sprintf("Test %02v", i), func(t *testing.T) {
			testString := trimmed[0:7]
			trimmed = trimmed[7:]

			testNum, err := strconv.ParseInt(testString[:2], 10, 8)
			assert.NoError(t, err, "error in parsing number: %s", testString[:2])
			assert.Equal(t, i+1, testNum, "unexpected test number")

			status := testString[3:5]
			assert.Equal(t, "ok", status, "status was not ok")
		})
	}
}

// TestInstructionsGB tests that the CPU passes all of the test instructions
// in the cpu_instrs rom in GB mode.
func TestInstructionsGB(t *testing.T) {
	cpuInstTest(t)
}

// TestInstructionsGB tests that the CPU passes all of the test instructions
// in the cpu_instrs rom in CGB mode (includes speed switches).
func TestInstructionsCGB(t *testing.T) {
	cpuInstTest(t, WithCGBEnabled())
}
<commit_msg>Reduce number of iteration in instructions test<commit_after>package gb

import (
	"fmt"
	"strconv"
	"strings"
	"testing"

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

const maxIterations = 3500

func cpuInstTest(t *testing.T, options ...GameboyOption) {
	output := ""
	transferOption := WithTransferFunction(func(val byte) {
		output += string(val)
	})
	options = append(options, transferOption)
	gb, err := NewGameboy("./../roms/cpu_instrs.gb", options...)
	require.NoError(t, err, "error in init gb %v", err)

	// Run the CPU until maxIterations iterations have passed.
	for i := 0; i < maxIterations; i++ {
		gb.Update()
	}

	// Trim off the title and any whitespace
	trimmed := strings.TrimSpace(strings.TrimPrefix(output, "cpu_instrs"))
	require.True(t, len(trimmed) >= 94, "did not finish getting output in %v iterations: %v", maxIterations, trimmed)

	for i := int64(0); i < 11; i++ {
		t.Run(fmt.Sprintf("Test %02v", i), func(t *testing.T) {
			testString := trimmed[0:7]
			trimmed = trimmed[7:]

			testNum, err := strconv.ParseInt(testString[:2], 10, 8)
			assert.NoError(t, err, "error in parsing number: %s", testString[:2])
			assert.Equal(t, i+1, testNum, "unexpected test number")

			status := testString[3:5]
			assert.Equal(t, "ok", status, "status was not ok")
		})
	}
}

// TestInstructionsGB tests that the CPU passes all of the test instructions
// in the cpu_instrs rom in GB mode.
func TestInstructionsGB(t *testing.T) {
	cpuInstTest(t)
}

// TestInstructionsGB tests that the CPU passes all of the test instructions
// in the cpu_instrs rom in CGB mode (includes speed switches).
func TestInstructionsCGB(t *testing.T) {
	cpuInstTest(t, WithCGBEnabled())
}
<|endoftext|>


<commit_before>package zfs

import (
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
)

type VersionType string

const (
	Bookmark VersionType = "bookmark"
	Snapshot             = "snapshot"
)

type FilesystemVersion struct {
	Type VersionType

	// Display name. Should not be used for identification, only for user output
	Name string

	// GUID as exported by ZFS. Uniquely identifies a snapshot across pools
	Guid uint64

	// The TXG in which the snapshot was created. For bookmarks,
	// this is the GUID of the snapshot it was initially tied to.
	CreateTXG uint64
}

type fsbyCreateTXG []FilesystemVersion

func (l fsbyCreateTXG) Len() int      { return len(l) }
func (l fsbyCreateTXG) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l fsbyCreateTXG) Less(i, j int) bool {
	return l[i].CreateTXG < l[j].CreateTXG
}

/* The sender (left) wants to know if the receiver (right) has more recent versions

	Left :         | C |
	Right: | A | B | C | D | E |
	=>   :         | C | D | E |

	Left:         | C |
	Right:			  | D | E |
	=>   :  <empty list>, no common ancestor

	Left :         | C | D | E |
	Right: | A | B | C |
	=>   :  <empty list>, the left has newer versions

	Left : | A | B | C |       | F |
	Right:         | C | D | E |
	=>   :         | C |	   | F | => diverged => <empty list>

IMPORTANT: since ZFS currently does not export dataset UUIDs, the best heuristic to
		   identify a filesystem version is the tuple (name,creation)
*/
type FilesystemDiff struct {

	// The increments required to get left up to right's most recent version
	// 0th element is the common ancestor, ordered by birthtime, oldest first
	// If empty, left and right are at same most recent version
	// If nil, there is no incremental path for left to get to right's most recent version
	// This means either (check Diverged field to determine which case we are in)
	//   a) no common ancestor (left deleted all the snapshots it previously transferred to right)
	//		=> consult MRCAPathRight and request initial retransfer after prep on left side
	//   b) divergence bewteen left and right (left made snapshots that right doesn't have)
	//   	=> check MRCAPathLeft and MRCAPathRight and decide what to do based on that
	IncrementalPath []FilesystemVersion

	// true if left and right diverged, false otherwise
	Diverged bool
	// If Diverged, contains path from left most recent common ancestor (mrca)
	// to most recent version on left
	// Otherwise: nil
	MRCAPathLeft []FilesystemVersion
	// If  Diverged, contains path from right most recent common ancestor (mrca)
	// to most recent version on right
	// If there is no common ancestor (i.e. not diverged), contains entire list of
	// versions on right
	MRCAPathRight []FilesystemVersion
}

func ZFSListFilesystemVersions(fs DatasetPath) (res []FilesystemVersion, err error) {
	var fieldLines [][]string
	fieldLines, err = ZFSList(
		[]string{"name", "guid", "createtxg"},
		"-r", "-d", "1",
		"-t", "bookmark,snapshot",
		"-s", "createtxg", fs.ToString())
	if err != nil {
		return
	}
	res = make([]FilesystemVersion, len(fieldLines))
	for i, line := range fieldLines {

		if len(line[0]) < 3 {
			err = errors.New(fmt.Sprintf("snapshot or bookmark name implausibly short: %s", line[0]))
			return
		}

		snapSplit := strings.SplitN(line[0], "@", 2)
		bookmarkSplit := strings.SplitN(line[0], "#", 2)
		if len(snapSplit)*len(bookmarkSplit) != 2 {
			err = errors.New(fmt.Sprintf("dataset cannot be snapshot and bookmark at the same time: %s", line[0]))
			return
		}

		var v FilesystemVersion
		if len(snapSplit) == 2 {
			v.Name = snapSplit[1]
			v.Type = Snapshot
		} else {
			v.Name = bookmarkSplit[1]
			v.Type = Bookmark
		}

		if v.Guid, err = strconv.ParseUint(line[1], 10, 64); err != nil {
			err = errors.New(fmt.Sprintf("cannot parse GUID: %s", err.Error()))
			return
		}

		if v.CreateTXG, err = strconv.ParseUint(line[2], 10, 64); err != nil {
			err = errors.New(fmt.Sprintf("cannot parse CreateTXG: %s", err.Error()))
			return
		}

		res[i] = v

	}
	return
}

// we must assume left and right are ordered ascendingly by ZFS_PROP_CREATETXG and that
// names are unique (bas ZFS_PROP_GUID replacement)
func MakeFilesystemDiff(left, right []FilesystemVersion) (diff FilesystemDiff) {

	// Assert both left and right are sorted by createtxg
	var leftSorted, rightSorted fsbyCreateTXG
	leftSorted = left
	rightSorted = right
	if !sort.IsSorted(leftSorted) {
		panic("cannot make filesystem diff: unsorted left")
	}
	if !sort.IsSorted(rightSorted) {
		panic("cannot make filesystem diff: unsorted right")
	}

	// Find most recent common ancestor by name, preferring snapshots over bookmars
	mrcaLeft := len(left) - 1
	var mrcaRight int
outer:
	for ; mrcaLeft >= 0; mrcaLeft-- {
		for i := len(right) - 1; i >= 0; i-- {
			if left[mrcaLeft].Guid == right[i].Guid {
				mrcaRight = i
				if i-1 >= 0 && right[i-1].Guid == right[i].Guid && right[i-1].Type == Snapshot {
					// prefer snapshots over bookmarks
					mrcaRight = i - 1
				}
				break outer
			}
		}
	}

	// no common ancestor?
	if mrcaLeft == -1 {
		diff = FilesystemDiff{
			IncrementalPath: nil,
			Diverged:        false,
			MRCAPathRight:   right,
		}
		return
	}

	// diverged?
	if mrcaLeft != len(left)-1 {
		diff = FilesystemDiff{
			IncrementalPath: nil,
			Diverged:        true,
			MRCAPathLeft:    left[mrcaLeft:],
			MRCAPathRight:   right[mrcaRight:],
		}
		return
	}

	if mrcaLeft != len(left)-1 {
		panic("invariant violated: mrca on left must be the last item in the left list")
	}

	// strip bookmarks going forward from right
	incPath := make([]FilesystemVersion, 0, len(right))
	incPath = append(incPath, right[mrcaRight])
	// right[mrcaRight] may be a bookmark if there's no equally named snapshot
	for i := mrcaRight + 1; i < len(right); i++ {
		if right[i].Type != Bookmark {
			incPath = append(incPath, right[i])
		}
	}

	diff = FilesystemDiff{
		IncrementalPath: incPath,
	}
	return
}
<commit_msg>zfs: FilesystemDiff: support empty left list = no common ancestor<commit_after>package zfs

import (
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
)

type VersionType string

const (
	Bookmark VersionType = "bookmark"
	Snapshot             = "snapshot"
)

type FilesystemVersion struct {
	Type VersionType

	// Display name. Should not be used for identification, only for user output
	Name string

	// GUID as exported by ZFS. Uniquely identifies a snapshot across pools
	Guid uint64

	// The TXG in which the snapshot was created. For bookmarks,
	// this is the GUID of the snapshot it was initially tied to.
	CreateTXG uint64
}

type fsbyCreateTXG []FilesystemVersion

func (l fsbyCreateTXG) Len() int      { return len(l) }
func (l fsbyCreateTXG) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l fsbyCreateTXG) Less(i, j int) bool {
	return l[i].CreateTXG < l[j].CreateTXG
}

/* The sender (left) wants to know if the receiver (right) has more recent versions

	Left :         | C |
	Right: | A | B | C | D | E |
	=>   :         | C | D | E |

	Left:         | C |
	Right:			  | D | E |
	=>   :  <empty list>, no common ancestor

	Left :         | C | D | E |
	Right: | A | B | C |
	=>   :  <empty list>, the left has newer versions

	Left : | A | B | C |       | F |
	Right:         | C | D | E |
	=>   :         | C |	   | F | => diverged => <empty list>

IMPORTANT: since ZFS currently does not export dataset UUIDs, the best heuristic to
		   identify a filesystem version is the tuple (name,creation)
*/
type FilesystemDiff struct {

	// The increments required to get left up to right's most recent version
	// 0th element is the common ancestor, ordered by birthtime, oldest first
	// If empty, left and right are at same most recent version
	// If nil, there is no incremental path for left to get to right's most recent version
	// This means either (check Diverged field to determine which case we are in)
	//   a) no common ancestor (left deleted all the snapshots it previously transferred to right)
	//		=> consult MRCAPathRight and request initial retransfer after prep on left side
	//   b) divergence bewteen left and right (left made snapshots that right doesn't have)
	//   	=> check MRCAPathLeft and MRCAPathRight and decide what to do based on that
	IncrementalPath []FilesystemVersion

	// true if left and right diverged, false otherwise
	Diverged bool
	// If Diverged, contains path from left most recent common ancestor (mrca)
	// to most recent version on left
	// Otherwise: nil
	MRCAPathLeft []FilesystemVersion
	// If  Diverged, contains path from right most recent common ancestor (mrca)
	// to most recent version on right
	// If there is no common ancestor (i.e. not diverged), contains entire list of
	// versions on right
	MRCAPathRight []FilesystemVersion
}

func ZFSListFilesystemVersions(fs DatasetPath) (res []FilesystemVersion, err error) {
	var fieldLines [][]string
	fieldLines, err = ZFSList(
		[]string{"name", "guid", "createtxg"},
		"-r", "-d", "1",
		"-t", "bookmark,snapshot",
		"-s", "createtxg", fs.ToString())
	if err != nil {
		return
	}
	res = make([]FilesystemVersion, len(fieldLines))
	for i, line := range fieldLines {

		if len(line[0]) < 3 {
			err = errors.New(fmt.Sprintf("snapshot or bookmark name implausibly short: %s", line[0]))
			return
		}

		snapSplit := strings.SplitN(line[0], "@", 2)
		bookmarkSplit := strings.SplitN(line[0], "#", 2)
		if len(snapSplit)*len(bookmarkSplit) != 2 {
			err = errors.New(fmt.Sprintf("dataset cannot be snapshot and bookmark at the same time: %s", line[0]))
			return
		}

		var v FilesystemVersion
		if len(snapSplit) == 2 {
			v.Name = snapSplit[1]
			v.Type = Snapshot
		} else {
			v.Name = bookmarkSplit[1]
			v.Type = Bookmark
		}

		if v.Guid, err = strconv.ParseUint(line[1], 10, 64); err != nil {
			err = errors.New(fmt.Sprintf("cannot parse GUID: %s", err.Error()))
			return
		}

		if v.CreateTXG, err = strconv.ParseUint(line[2], 10, 64); err != nil {
			err = errors.New(fmt.Sprintf("cannot parse CreateTXG: %s", err.Error()))
			return
		}

		res[i] = v

	}
	return
}

// we must assume left and right are ordered ascendingly by ZFS_PROP_CREATETXG and that
// names are unique (bas ZFS_PROP_GUID replacement)
func MakeFilesystemDiff(left, right []FilesystemVersion) (diff FilesystemDiff) {

	if right == nil {
		panic("right must not be nil")
	}
	if left == nil { // treat like no common ancestor
		diff = FilesystemDiff{
			IncrementalPath: nil,
			Diverged:        false,
			MRCAPathRight:   right,
		}
	}

	// Assert both left and right are sorted by createtxg
	var leftSorted, rightSorted fsbyCreateTXG
	leftSorted = left
	rightSorted = right
	if !sort.IsSorted(leftSorted) {
		panic("cannot make filesystem diff: unsorted left")
	}
	if !sort.IsSorted(rightSorted) {
		panic("cannot make filesystem diff: unsorted right")
	}

	// Find most recent common ancestor by name, preferring snapshots over bookmars
	mrcaLeft := len(left) - 1
	var mrcaRight int
outer:
	for ; mrcaLeft >= 0; mrcaLeft-- {
		for i := len(right) - 1; i >= 0; i-- {
			if left[mrcaLeft].Guid == right[i].Guid {
				mrcaRight = i
				if i-1 >= 0 && right[i-1].Guid == right[i].Guid && right[i-1].Type == Snapshot {
					// prefer snapshots over bookmarks
					mrcaRight = i - 1
				}
				break outer
			}
		}
	}

	// no common ancestor?
	if mrcaLeft == -1 {
		diff = FilesystemDiff{
			IncrementalPath: nil,
			Diverged:        false,
			MRCAPathRight:   right,
		}
		return
	}

	// diverged?
	if mrcaLeft != len(left)-1 {
		diff = FilesystemDiff{
			IncrementalPath: nil,
			Diverged:        true,
			MRCAPathLeft:    left[mrcaLeft:],
			MRCAPathRight:   right[mrcaRight:],
		}
		return
	}

	if mrcaLeft != len(left)-1 {
		panic("invariant violated: mrca on left must be the last item in the left list")
	}

	// strip bookmarks going forward from right
	incPath := make([]FilesystemVersion, 0, len(right))
	incPath = append(incPath, right[mrcaRight])
	// right[mrcaRight] may be a bookmark if there's no equally named snapshot
	for i := mrcaRight + 1; i < len(right); i++ {
		if right[i].Type != Bookmark {
			incPath = append(incPath, right[i])
		}
	}

	diff = FilesystemDiff{
		IncrementalPath: incPath,
	}
	return
}
<|endoftext|>


<commit_before>// Copyright 2015 The Serulian Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package es5

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/robertkrimen/otto/ast"
	"github.com/robertkrimen/otto/parser"
	"github.com/serulian/compiler/generator/es5/templater"
)

// formatSource parses and formats the given ECMAScript source code. Panics if the
// code cannot be parsed.
func formatSource(source string) string {
	// Parse the ES source.
	program, err := parser.ParseFile(nil, "", source, 0)
	if err != nil {
		fmt.Printf("Parse error in source: %v", source)
		panic(err)
	}

	// Reformat nicely.
	formatter := &sourceFormatter{
		templater:        templater.New(),
		indentationLevel: 0,
		hasNewline:       true,
	}

	formatter.FormatProgram(program)
	return formatter.buf.String()
}

// sourceFormatter formats an ES parse tree.
type sourceFormatter struct {
	templater        *templater.Templater // The templater.
	buf              bytes.Buffer         // The buffer for the new source code.
	indentationLevel int                  // The current indentation level.
	hasNewline       bool                 // Whether there is a newline at the end of the buffer.
}

// indent increases the current indentation.
func (sf *sourceFormatter) indent() {
	sf.indentationLevel = sf.indentationLevel + 1
}

// dedent decreases the current indentation.
func (sf *sourceFormatter) dedent() {
	sf.indentationLevel = sf.indentationLevel - 1
}

// append adds the given value to the buffer, indenting as necessary.
func (sf *sourceFormatter) append(value string) {
	for _, currentRune := range value {
		if currentRune == '\n' {
			sf.buf.WriteRune('\n')
			sf.hasNewline = true
			continue
		}

		if sf.hasNewline {
			sf.buf.WriteString(strings.Repeat("  ", sf.indentationLevel))
			sf.hasNewline = false
		}

		sf.buf.WriteRune(currentRune)
	}
}

// appendLine adds a newline.
func (sf *sourceFormatter) appendLine() {
	sf.append("\n")
}

// FormatProgram formats a parsed ES program.
func (sf *sourceFormatter) FormatProgram(program *ast.Program) {
	sf.FormatStatementList(program.Body)
}

// FormatExpressionList formats a list of expressions.
func (sf *sourceFormatter) FormatExpressionList(expressions []ast.Expression) {
	for index, expression := range expressions {
		if index > 0 {
			sf.append(", ")
		}

		sf.FormatExpression(expression)
	}
}

// FormatIdentifierList formats a list of identifiers.
func (sf *sourceFormatter) FormatIdentifierList(identifiers []*ast.Identifier) {
	for index, identifier := range identifiers {
		if index > 0 {
			sf.append(", ")
		}

		sf.FormatExpression(identifier)
	}
}

// FormatExpression formats an ES expression.
func (sf *sourceFormatter) FormatExpression(expression ast.Expression) {
	switch e := expression.(type) {

	// ArrayLiteral
	case *ast.ArrayLiteral:
		sf.append("[")
		sf.FormatExpressionList(e.Value)
		sf.append("]")

	// AssignExpression
	case *ast.AssignExpression:
		sf.FormatExpression(e.Left)
		sf.append(" ")
		sf.append(e.Operator.String())
		sf.append(" ")
		sf.FormatExpression(e.Right)

	// BinaryExpression
	case *ast.BinaryExpression:
		sf.FormatExpression(e.Left)
		sf.append(" ")
		sf.append(e.Operator.String())
		sf.append(" ")
		sf.FormatExpression(e.Right)

	// BooleanLiteral
	case *ast.BooleanLiteral:
		sf.append(e.Literal)

	// BracketExpression
	case *ast.BracketExpression:
		sf.FormatExpression(e.Left)
		sf.append("[")
		sf.FormatExpression(e.Member)
		sf.append("]")

	// CallExpression
	case *ast.CallExpression:
		sf.FormatExpression(e.Callee)
		sf.append("(")
		sf.FormatExpressionList(e.ArgumentList)
		sf.append(")")

	// ConditionalExpression
	case *ast.ConditionalExpression:
		sf.FormatExpression(e.Test)
		sf.append(" ? ")
		sf.FormatExpression(e.Consequent)
		sf.append(" : ")
		sf.FormatExpression(e.Alternate)

	// DotExpression
	case *ast.DotExpression:
		sf.FormatExpression(e.Left)
		sf.append(".")
		sf.append(e.Identifier.Name)

	// FunctionLiteral
	case *ast.FunctionLiteral:
		sf.append("function")
		if e.Name != nil {
			sf.append(" ")
			sf.append(e.Name.Name)
		}

		sf.append(" (")
		sf.FormatIdentifierList(e.ParameterList.List)
		sf.append(") ")
		sf.FormatStatement(e.Body)

	// Identifer
	case *ast.Identifier:
		sf.append(e.Name)

	// NewExpression
	case *ast.NewExpression:
		sf.append("new ")
		sf.FormatExpression(e.Callee)
		sf.append("(")
		sf.FormatExpressionList(e.ArgumentList)
		sf.append(")")

	// NullLiteral
	case *ast.NullLiteral:
		sf.append("null")

	// NumberLiteral
	case *ast.NumberLiteral:
		sf.append(e.Literal)

	// ObjectLiteral
	case *ast.ObjectLiteral:
		sf.append("{")
		sf.appendLine()
		sf.indent()

		for _, value := range e.Value {
			sf.append(value.Key)
			sf.append(": ")
			sf.FormatExpression(value.Value)
			sf.append(",")
			sf.appendLine()
		}

		sf.dedent()
		sf.append("}")

	// RegExpLiteral
	case *ast.RegExpLiteral:
		sf.append(e.Literal)

	// StringLiteral
	case *ast.StringLiteral:
		sf.append(e.Literal)

	// ThisExpression
	case *ast.ThisExpression:
		sf.append("this")

	// SequenceExpression:
	case *ast.SequenceExpression:
		sf.FormatExpressionList(e.Sequence)

	// UnaryExpression
	case *ast.UnaryExpression:
		if e.Postfix {
			sf.FormatExpression(e.Operand)
			sf.append("(")
			sf.append(e.Operator.String())
			sf.append(")")
		} else {
			if e.Operator.String() == "delete" {
				sf.append(e.Operator.String())
				sf.append(" ")
				sf.FormatExpression(e.Operand)
			} else {
				sf.append(e.Operator.String())
				sf.append("(")
				sf.FormatExpression(e.Operand)
				sf.append(")")
			}
		}

	// VariableExpression
	case *ast.VariableExpression:
		sf.append("var ")
		sf.append(e.Name)
		if e.Initializer != nil {
			sf.append(" = ")
			sf.FormatExpression(e.Initializer)
		}

	default:
		panic(fmt.Sprintf("Unknown expression AST node: %T", e))
	}
}

// FormatStatementList formats a list of statements.
func (sf *sourceFormatter) FormatStatementList(statements []ast.Statement) {
loop:
	for _, statement := range statements {
		sf.FormatStatement(statement)
		sf.appendLine()

		// If the statement is a terminating statement, skip the rest of the block.
		switch statement.(type) {
		case *ast.ReturnStatement:
			break loop

		case *ast.BranchStatement:
			break loop
		}
	}
}

// FormatStatement formats an ES statement.
func (sf *sourceFormatter) FormatStatement(statement ast.Statement) {
	switch s := statement.(type) {

	// Block
	case *ast.BlockStatement:
		sf.append("{")
		sf.appendLine()
		sf.indent()
		sf.FormatStatementList(s.List)
		sf.dedent()
		sf.append("}")

	// CaseStatement
	case *ast.CaseStatement:
		if s.Test != nil {
			sf.append("case ")
			sf.FormatExpression(s.Test)
			sf.append(":")
			sf.appendLine()
			sf.indent()
			sf.FormatStatementList(s.Consequent)
			sf.dedent()
		} else {
			sf.append("default:")
			sf.appendLine()
			sf.indent()
			sf.FormatStatementList(s.Consequent)
			sf.dedent()
		}

	// CatchStatement
	case *ast.CatchStatement:
		sf.append(" catch (")
		sf.append(s.Parameter.Name)
		sf.append(") ")
		sf.FormatStatement(s.Body)

	// BranchStatement
	case *ast.BranchStatement:
		sf.append(s.Token.String())
		if s.Label != nil {
			sf.append(" ")
			sf.append(s.Label.Name)
		}
		sf.append(";")

	// DebuggerStatement
	case *ast.DebuggerStatement:
		sf.append("debugger")
		sf.append(";")

	// DoWhileStatement
	case *ast.DoWhileStatement:
		sf.append("do ")
		sf.FormatStatement(s.Body)
		sf.appendLine()
		sf.append("while (")
		sf.FormatExpression(s.Test)
		sf.append(")")
		sf.append(";")

	// EmptyStatement
	case *ast.EmptyStatement:
		break

	// ExpressionStatement
	case *ast.ExpressionStatement:
		sf.FormatExpression(s.Expression)
		sf.append(";")

	// ForStatement
	case *ast.ForStatement:
		sf.append("for (")
		if s.Initializer != nil {
			sf.FormatExpression(s.Initializer)
		}
		sf.append("; ")

		if s.Test != nil {
			sf.FormatExpression(s.Test)
		}
		sf.append("; ")

		if s.Update != nil {
			sf.FormatExpression(s.Update)
		}
		sf.append(") ")
		sf.FormatStatement(s.Body)

	// ForInStatement
	case *ast.ForInStatement:
		sf.append("for (")
		sf.FormatExpression(s.Into)
		sf.append(" in ")
		sf.FormatExpression(s.Source)
		sf.append(") ")
		sf.FormatStatement(s.Body)

	// IfStatement
	case *ast.IfStatement:
		sf.append("if (")
		sf.FormatExpression(s.Test)
		sf.append(") ")
		sf.FormatStatement(s.Consequent)

		if s.Alternate != nil {
			sf.append(" else ")
			sf.FormatStatement(s.Alternate)
		}

	// ReturnStatement
	case *ast.ReturnStatement:
		sf.append("return")
		if s.Argument != nil {
			sf.append(" ")
			sf.FormatExpression(s.Argument)
		}
		sf.append(";")

	// SwitchStatement
	case *ast.SwitchStatement:
		sf.append("switch (")
		sf.FormatExpression(s.Discriminant)
		sf.append(") {")
		sf.appendLine()
		sf.indent()

		for index, caseStatement := range s.Body {
			if index > 0 {
				sf.appendLine()
			}

			sf.FormatStatement(caseStatement)
		}

		sf.dedent()
		sf.append("}")

	// ThrowStatement
	case *ast.ThrowStatement:
		sf.append("throw ")
		sf.FormatExpression(s.Argument)
		sf.append(";")

	// TryStatement
	case *ast.TryStatement:
		sf.append("try ")
		sf.FormatStatement(s.Body)

		if s.Catch != nil {
			sf.FormatStatement(s.Catch)
		}

		if s.Finally != nil {
			sf.append("finally ")
			sf.FormatStatement(s.Finally)
		}

	// VariableStatement
	case *ast.VariableStatement:
		sf.FormatExpressionList(s.List)
		sf.append(";")

	// WhileStatement
	case *ast.WhileStatement:
		sf.append("while (")
		sf.FormatExpression(s.Test)
		sf.append(") ")
		sf.FormatStatement(s.Body)

	default:
		panic(fmt.Sprintf("Unknown statement AST node: %v", s))
	}
}
<commit_msg>Better formatting of expressions<commit_after>// Copyright 2015 The Serulian Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package es5

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/robertkrimen/otto/ast"
	"github.com/robertkrimen/otto/parser"
	"github.com/serulian/compiler/generator/es5/templater"
)

// formatSource parses and formats the given ECMAScript source code. Panics if the
// code cannot be parsed.
func formatSource(source string) string {
	// Parse the ES source.
	program, err := parser.ParseFile(nil, "", source, 0)
	if err != nil {
		fmt.Printf("Parse error in source: %v", source)
		panic(err)
	}

	// Reformat nicely.
	formatter := &sourceFormatter{
		templater:        templater.New(),
		indentationLevel: 0,
		hasNewline:       true,
	}

	formatter.FormatProgram(program)
	return formatter.buf.String()
}

// sourceFormatter formats an ES parse tree.
type sourceFormatter struct {
	templater        *templater.Templater // The templater.
	buf              bytes.Buffer         // The buffer for the new source code.
	indentationLevel int                  // The current indentation level.
	hasNewline       bool                 // Whether there is a newline at the end of the buffer.
}

// indent increases the current indentation.
func (sf *sourceFormatter) indent() {
	sf.indentationLevel = sf.indentationLevel + 1
}

// dedent decreases the current indentation.
func (sf *sourceFormatter) dedent() {
	sf.indentationLevel = sf.indentationLevel - 1
}

// append adds the given value to the buffer, indenting as necessary.
func (sf *sourceFormatter) append(value string) {
	for _, currentRune := range value {
		if currentRune == '\n' {
			sf.buf.WriteRune('\n')
			sf.hasNewline = true
			continue
		}

		if sf.hasNewline {
			sf.buf.WriteString(strings.Repeat("  ", sf.indentationLevel))
			sf.hasNewline = false
		}

		sf.buf.WriteRune(currentRune)
	}
}

// appendLine adds a newline.
func (sf *sourceFormatter) appendLine() {
	sf.append("\n")
}

// FormatProgram formats a parsed ES program.
func (sf *sourceFormatter) FormatProgram(program *ast.Program) {
	sf.FormatStatementList(program.Body)
}

// FormatExpressionList formats a list of expressions.
func (sf *sourceFormatter) FormatExpressionList(expressions []ast.Expression) {
	for index, expression := range expressions {
		if index > 0 {
			sf.append(", ")
		}

		sf.FormatExpression(expression)
	}
}

// FormatIdentifierList formats a list of identifiers.
func (sf *sourceFormatter) FormatIdentifierList(identifiers []*ast.Identifier) {
	for index, identifier := range identifiers {
		if index > 0 {
			sf.append(", ")
		}

		sf.FormatExpression(identifier)
	}
}

// FormatExpression formats an ES expression.
func (sf *sourceFormatter) FormatExpression(expression ast.Expression) {
	switch e := expression.(type) {

	// ArrayLiteral
	case *ast.ArrayLiteral:
		sf.append("[")
		sf.FormatExpressionList(e.Value)
		sf.append("]")

	// AssignExpression
	case *ast.AssignExpression:
		sf.FormatExpression(e.Left)
		sf.append(" ")
		sf.append(e.Operator.String())
		sf.append(" ")
		sf.FormatExpression(e.Right)

	// BinaryExpression
	case *ast.BinaryExpression:
		sf.appendOptionalOpenParen(e.Left)
		sf.FormatExpression(e.Left)
		sf.appendOptionalCloseParen(e.Left)
		sf.append(" ")
		sf.append(e.Operator.String())
		sf.append(" ")
		sf.appendOptionalOpenParen(e.Right)
		sf.FormatExpression(e.Right)
		sf.appendOptionalCloseParen(e.Right)

	// BooleanLiteral
	case *ast.BooleanLiteral:
		sf.append(e.Literal)

	// BracketExpression
	case *ast.BracketExpression:
		sf.FormatExpression(e.Left)
		sf.append("[")
		sf.FormatExpression(e.Member)
		sf.append("]")

	// CallExpression
	case *ast.CallExpression:
		sf.FormatExpression(e.Callee)
		sf.append("(")
		sf.FormatExpressionList(e.ArgumentList)
		sf.append(")")

	// ConditionalExpression
	case *ast.ConditionalExpression:
		sf.FormatExpression(e.Test)
		sf.append(" ? ")
		sf.FormatExpression(e.Consequent)
		sf.append(" : ")
		sf.FormatExpression(e.Alternate)

	// DotExpression
	case *ast.DotExpression:
		sf.FormatExpression(e.Left)
		sf.append(".")
		sf.append(e.Identifier.Name)

	// FunctionLiteral
	case *ast.FunctionLiteral:
		sf.append("function")
		if e.Name != nil {
			sf.append(" ")
			sf.append(e.Name.Name)
		}

		sf.append(" (")
		sf.FormatIdentifierList(e.ParameterList.List)
		sf.append(") ")
		sf.FormatStatement(e.Body)

	// Identifer
	case *ast.Identifier:
		sf.append(e.Name)

	// NewExpression
	case *ast.NewExpression:
		sf.append("new ")
		sf.FormatExpression(e.Callee)
		sf.append("(")
		sf.FormatExpressionList(e.ArgumentList)
		sf.append(")")

	// NullLiteral
	case *ast.NullLiteral:
		sf.append("null")

	// NumberLiteral
	case *ast.NumberLiteral:
		sf.append(e.Literal)

	// ObjectLiteral
	case *ast.ObjectLiteral:
		sf.append("{")
		sf.appendLine()
		sf.indent()

		for _, value := range e.Value {
			sf.append(value.Key)
			sf.append(": ")
			sf.FormatExpression(value.Value)
			sf.append(",")
			sf.appendLine()
		}

		sf.dedent()
		sf.append("}")

	// RegExpLiteral
	case *ast.RegExpLiteral:
		sf.append(e.Literal)

	// StringLiteral
	case *ast.StringLiteral:
		sf.append(e.Literal)

	// ThisExpression
	case *ast.ThisExpression:
		sf.append("this")

	// SequenceExpression:
	case *ast.SequenceExpression:
		sf.FormatExpressionList(e.Sequence)

	// UnaryExpression
	case *ast.UnaryExpression:
		if e.Postfix {
			sf.FormatExpression(e.Operand)
			sf.appendOptionalOpenParen(e.Operand)
			sf.append(e.Operator.String())
			sf.appendOptionalCloseParen(e.Operand)
		} else {
			if e.Operator.String() == "delete" {
				sf.append(e.Operator.String())
				sf.append(" ")
				sf.FormatExpression(e.Operand)
			} else {
				sf.append(e.Operator.String())
				sf.appendOptionalOpenParen(e.Operand)
				sf.FormatExpression(e.Operand)
				sf.appendOptionalCloseParen(e.Operand)
			}
		}

	// VariableExpression
	case *ast.VariableExpression:
		sf.append("var ")
		sf.append(e.Name)
		if e.Initializer != nil {
			sf.append(" = ")
			sf.FormatExpression(e.Initializer)
		}

	default:
		panic(fmt.Sprintf("Unknown expression AST node: %T", e))
	}
}

// requiresParen returns true if the given expression requires parenthesis for ensuring
// operation ordering.
func (sf *sourceFormatter) requiresParen(expr ast.Expression) bool {
	_, ok := expr.(*ast.BinaryExpression)
	return ok
}

// appendOptionalOpenParen will append an open parenthesis iff the expression requires it.
func (sf *sourceFormatter) appendOptionalOpenParen(expr ast.Expression) {
	if sf.requiresParen(expr) {
		sf.append("(")
	}
}

// appendOptionalCloseParen will append a close parenthesis iff the expression requires it.
func (sf *sourceFormatter) appendOptionalCloseParen(expr ast.Expression) {
	if sf.requiresParen(expr) {
		sf.append(")")
	}
}

// FormatStatementList formats a list of statements.
func (sf *sourceFormatter) FormatStatementList(statements []ast.Statement) {
loop:
	for _, statement := range statements {
		sf.FormatStatement(statement)
		sf.appendLine()

		// If the statement is a terminating statement, skip the rest of the block.
		switch statement.(type) {
		case *ast.ReturnStatement:
			break loop

		case *ast.BranchStatement:
			break loop
		}
	}
}

// FormatStatement formats an ES statement.
func (sf *sourceFormatter) FormatStatement(statement ast.Statement) {
	switch s := statement.(type) {

	// Block
	case *ast.BlockStatement:
		sf.append("{")
		sf.appendLine()
		sf.indent()
		sf.FormatStatementList(s.List)
		sf.dedent()
		sf.append("}")

	// CaseStatement
	case *ast.CaseStatement:
		if s.Test != nil {
			sf.append("case ")
			sf.FormatExpression(s.Test)
			sf.append(":")
			sf.appendLine()
			sf.indent()
			sf.FormatStatementList(s.Consequent)
			sf.dedent()
		} else {
			sf.append("default:")
			sf.appendLine()
			sf.indent()
			sf.FormatStatementList(s.Consequent)
			sf.dedent()
		}

	// CatchStatement
	case *ast.CatchStatement:
		sf.append(" catch (")
		sf.append(s.Parameter.Name)
		sf.append(") ")
		sf.FormatStatement(s.Body)

	// BranchStatement
	case *ast.BranchStatement:
		sf.append(s.Token.String())
		if s.Label != nil {
			sf.append(" ")
			sf.append(s.Label.Name)
		}
		sf.append(";")

	// DebuggerStatement
	case *ast.DebuggerStatement:
		sf.append("debugger")
		sf.append(";")

	// DoWhileStatement
	case *ast.DoWhileStatement:
		sf.append("do ")
		sf.FormatStatement(s.Body)
		sf.appendLine()
		sf.append("while (")
		sf.FormatExpression(s.Test)
		sf.append(")")
		sf.append(";")

	// EmptyStatement
	case *ast.EmptyStatement:
		break

	// ExpressionStatement
	case *ast.ExpressionStatement:
		sf.FormatExpression(s.Expression)
		sf.append(";")

	// ForStatement
	case *ast.ForStatement:
		sf.append("for (")
		if s.Initializer != nil {
			sf.FormatExpression(s.Initializer)
		}
		sf.append("; ")

		if s.Test != nil {
			sf.FormatExpression(s.Test)
		}
		sf.append("; ")

		if s.Update != nil {
			sf.FormatExpression(s.Update)
		}
		sf.append(") ")
		sf.FormatStatement(s.Body)

	// ForInStatement
	case *ast.ForInStatement:
		sf.append("for (")
		sf.FormatExpression(s.Into)
		sf.append(" in ")
		sf.FormatExpression(s.Source)
		sf.append(") ")
		sf.FormatStatement(s.Body)

	// IfStatement
	case *ast.IfStatement:
		sf.append("if (")
		sf.FormatExpression(s.Test)
		sf.append(") ")
		sf.FormatStatement(s.Consequent)

		if s.Alternate != nil {
			sf.append(" else ")
			sf.FormatStatement(s.Alternate)
		}

	// ReturnStatement
	case *ast.ReturnStatement:
		sf.append("return")
		if s.Argument != nil {
			sf.append(" ")
			sf.FormatExpression(s.Argument)
		}
		sf.append(";")

	// SwitchStatement
	case *ast.SwitchStatement:
		sf.append("switch (")
		sf.FormatExpression(s.Discriminant)
		sf.append(") {")
		sf.appendLine()
		sf.indent()

		for index, caseStatement := range s.Body {
			if index > 0 {
				sf.appendLine()
			}

			sf.FormatStatement(caseStatement)
		}

		sf.dedent()
		sf.append("}")

	// ThrowStatement
	case *ast.ThrowStatement:
		sf.append("throw ")
		sf.FormatExpression(s.Argument)
		sf.append(";")

	// TryStatement
	case *ast.TryStatement:
		sf.append("try ")
		sf.FormatStatement(s.Body)

		if s.Catch != nil {
			sf.FormatStatement(s.Catch)
		}

		if s.Finally != nil {
			sf.append("finally ")
			sf.FormatStatement(s.Finally)
		}

	// VariableStatement
	case *ast.VariableStatement:
		sf.FormatExpressionList(s.List)
		sf.append(";")

	// WhileStatement
	case *ast.WhileStatement:
		sf.append("while (")
		sf.FormatExpression(s.Test)
		sf.append(") ")
		sf.FormatStatement(s.Body)

	default:
		panic(fmt.Sprintf("Unknown statement AST node: %v", s))
	}
}
<|endoftext|>