<commit_before>package arn

import (
	"sort"
)

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

	user *User
}

// Find returns the list item with the specified anime ID, if available.
func (list *AnimeList) Find(animeID string) *AnimeListItem {
	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.Items = append(list.Items, item)
		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 {
	if list.user == nil {
		list.user, _ = GetUser(list.UserID)
	}

	return list.user
}

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

		if a.Status != AnimeListStatusWatching && b.Status != AnimeListStatusWatching {
			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
	})
}

// 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{},
	}

	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{},
	}

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

	return statusToList
}

// PrefetchAnime loads all the anime objects from the list into memory.
func (list *AnimeList) PrefetchAnime() {
	animeIDList := make([]string, len(list.Items), len(list.Items))

	for i, item := range list.Items {
		animeIDList[i] = item.AnimeID
	}

	// Prefetch anime objects
	animeObjects, _ := DB.GetMany("Anime", animeIDList)
	prefetchedAnime := animeObjects.([]*Anime)

	for i, anime := range prefetchedAnime {
		list.Items[i].anime = anime
	}
}

// NormalizeRatings normalizes all ratings so that they are perfectly stretched among the full scale.
func (list *AnimeList) NormalizeRatings() {
	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()
	}
}

// StreamAnimeLists returns a stream of all anime.
func StreamAnimeLists() (chan *AnimeList, error) {
	objects, err := DB.All("AnimeList")
	return objects.(chan *AnimeList), err
}

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

	stream, err := StreamAnimeLists()

	if err != nil {
		return nil, err
	}

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

	return all, nil
}

// GetAnimeList ...
func GetAnimeList(userID string) (*AnimeList, error) {
	animeList := &AnimeList{
		UserID: userID,
		Items:  []*AnimeListItem{},
	}

	m, err := DB.GetMap("AnimeList", userID)

	if err != nil {
		return nil, err
	}

	itemList := m["items"].([]interface{})

	for _, itemMap := range itemList {
		item := itemMap.(map[interface{}]interface{})
		ratingMap := item["rating"].(map[interface{}]interface{})
		newItem := &AnimeListItem{
			AnimeID:      item["animeId"].(string),
			Status:       item["status"].(string),
			Episodes:     item["episodes"].(int),
			Notes:        item["notes"].(string),
			RewatchCount: item["rewatchCount"].(int),
			Private:      item["private"].(int) != 0,
			Edited:       item["edited"].(string),
			Created:      item["created"].(string),
			Rating: &AnimeRating{
				Overall:    ratingMap["overall"].(float64),
				Story:      ratingMap["story"].(float64),
				Visuals:    ratingMap["visuals"].(float64),
				Soundtrack: ratingMap["soundtrack"].(float64),
			},
		}

		animeList.Items = append(animeList.Items, newItem)
	}

	return animeList, nil
}
<commit_msg>Special sorting for planned list<commit_after>package arn

import (
	"sort"
)

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

	user *User
}

// Find returns the list item with the specified anime ID, if available.
func (list *AnimeList) Find(animeID string) *AnimeListItem {
	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.Items = append(list.Items, item)
		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 {
	if list.user == nil {
		list.user, _ = GetUser(list.UserID)
	}

	return list.user
}

// Sort ...
func (list *AnimeList) Sort() {
	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
	})
}

// 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{},
	}

	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{},
	}

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

	return statusToList
}

// PrefetchAnime loads all the anime objects from the list into memory.
func (list *AnimeList) PrefetchAnime() {
	animeIDList := make([]string, len(list.Items), len(list.Items))

	for i, item := range list.Items {
		animeIDList[i] = item.AnimeID
	}

	// Prefetch anime objects
	animeObjects, _ := DB.GetMany("Anime", animeIDList)
	prefetchedAnime := animeObjects.([]*Anime)

	for i, anime := range prefetchedAnime {
		list.Items[i].anime = anime
	}
}

// NormalizeRatings normalizes all ratings so that they are perfectly stretched among the full scale.
func (list *AnimeList) NormalizeRatings() {
	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()
	}
}

// StreamAnimeLists returns a stream of all anime.
func StreamAnimeLists() (chan *AnimeList, error) {
	objects, err := DB.All("AnimeList")
	return objects.(chan *AnimeList), err
}

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

	stream, err := StreamAnimeLists()

	if err != nil {
		return nil, err
	}

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

	return all, nil
}

// GetAnimeList ...
func GetAnimeList(userID string) (*AnimeList, error) {
	animeList := &AnimeList{
		UserID: userID,
		Items:  []*AnimeListItem{},
	}

	m, err := DB.GetMap("AnimeList", userID)

	if err != nil {
		return nil, err
	}

	itemList := m["items"].([]interface{})

	for _, itemMap := range itemList {
		item := itemMap.(map[interface{}]interface{})
		ratingMap := item["rating"].(map[interface{}]interface{})
		newItem := &AnimeListItem{
			AnimeID:      item["animeId"].(string),
			Status:       item["status"].(string),
			Episodes:     item["episodes"].(int),
			Notes:        item["notes"].(string),
			RewatchCount: item["rewatchCount"].(int),
			Private:      item["private"].(int) != 0,
			Edited:       item["edited"].(string),
			Created:      item["created"].(string),
			Rating: &AnimeRating{
				Overall:    ratingMap["overall"].(float64),
				Story:      ratingMap["story"].(float64),
				Visuals:    ratingMap["visuals"].(float64),
				Soundtrack: ratingMap["soundtrack"].(float64),
			},
		}

		animeList.Items = append(animeList.Items, newItem)
	}

	return animeList, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"syscall"
	"time"

	"github.com/tcnksm/go-gitconfig"
)

var (
	config  Config
	version string

	sourceTemplate = []byte(`package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, playground")
}
`)
)

type Config struct {
	List bool
	Edit bool
}

func playgroundRootPath() (string, error) {
	root, err := gitconfig.Global("goplay.root")
	if err == nil {
		return root, nil
	}

	if err != gitconfig.ErrNotFound {
		return "", err
	}

	return "/tmp/goplay", nil
}

func listPlaygroundDirs() error {
	root, err := playgroundRootPath()
	if err != nil {
		return err
	}

	entries, err := ioutil.ReadDir(root)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}

	names := []string{}

	for _, e := range entries {
		if e.IsDir() {
			names = append(names, e.Name())
		}
	}

	sort.Sort(sort.Reverse(sort.StringSlice(names)))
	for _, name := range names {
		abs, err := filepath.Abs(filepath.Join(root, name))
		if err != nil {
			return err
		}
		fmt.Println(abs)
	}
	return nil
}

func createPlaygroundDir() (string, error) {
	root, err := playgroundRootPath()
	if err != nil {
		return "", err
	}

	name := time.Now().Format("2006-01-02_15-04-05")
	path := filepath.Join(root, name)
	path, err = filepath.Abs(path)
	if err != nil {
		return "", err
	}

	if err := os.MkdirAll(path, 0700); err != nil {
		return "", err
	}

	return path, nil
}

func parseConfig() error {
	flag.BoolVar(&config.Edit, "e", false, "Open created script with editor")
	flag.BoolVar(&config.List, "l", false, "List park directries")
	v := flag.Bool("version", false, "print app version")
	flag.Parse()

	if *v {
		fmt.Printf("goplay: %s\n", version)
		os.Exit(0)
	}

	return nil
}

func getenv(name, defaultValue string) string {
	v := os.Getenv(name)
	if v == "" {
		return defaultValue
	}
	return v
}

func getEditorCommand() (string, error) {
	if v := os.Getenv("EDITOR"); v != "" {
		return v, nil
	}

	if v := os.Getenv("VISUAL"); v != "" {
		return v, nil
	}

	// in Unix-like OS, try to find editors
	for _, v := range []string{"editor", "vi", "nano"} {
		_, err := exec.LookPath(v)
		if err != nil {
			return v, nil
		}
	}
	return "", fmt.Errorf("editor not found, please set $EDITOR environment variable")
}

func createGoFile(dirPath string) (string, error) {
	path, err := filepath.Abs(filepath.Join(dirPath, "main.go"))
	if err != nil {
		return "", err
	}

	if err := ioutil.WriteFile(path, sourceTemplate, 0600); err != nil {
		return "", err
	}
	return path, err
}

func gotoPlayground(dirPath, filePath string) error {
	shell, err := exec.LookPath(getenv("SHELL", "/bin/sh"))
	if err != nil {
		return err
	}

	if err := os.Chdir(dirPath); err != nil {
		return err
	}

	fmt.Fprintf(os.Stderr, "\tcd %s\n", dirPath)

	var argv []string
	if config.Edit {
		editor, err := getEditorCommand()
		if err != nil {
			return err
		}

		fmt.Fprintf(os.Stderr, "\t%s %s\n", editor, filePath)
		argv = []string{
			shell,
			"-c",
			fmt.Sprintf("%s %s; exec %s", editor, filePath, shell),
		}
	} else {
		argv = []string{shell}
	}

	libs, err := filepath.Abs(filepath.Join(dirPath, "golibs"))
	if err != nil {
		return err
	}

	if err := os.Setenv("GOPATH", libs+":"+os.Getenv("GOPATH")); err != nil {
		return err
	}

	return syscall.Exec(shell, argv, syscall.Environ())
}

func main() {
	abortOnError := func(err error) {
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}

	err := parseConfig()
	abortOnError(err)

	if config.List {
		err = listPlaygroundDirs()
		abortOnError(err)
		os.Exit(0)
	}

	dirPath, err := createPlaygroundDir()
	abortOnError(err)

	filePath, err := createGoFile(dirPath)
	abortOnError(err)

	err = gotoPlayground(dirPath, filePath)
	abortOnError(err)

	os.Exit(0)
}
<commit_msg>Fixed logic to search editor<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"syscall"
	"time"

	"github.com/tcnksm/go-gitconfig"
)

var (
	config  Config
	version string

	sourceTemplate = []byte(`package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, playground")
}
`)
)

type Config struct {
	List bool
	Edit bool
}

func playgroundRootPath() (string, error) {
	root, err := gitconfig.Global("goplay.root")
	if err == nil {
		return root, nil
	}

	if err != gitconfig.ErrNotFound {
		return "", err
	}

	return "/tmp/goplay", nil
}

func listPlaygroundDirs() error {
	root, err := playgroundRootPath()
	if err != nil {
		return err
	}

	entries, err := ioutil.ReadDir(root)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}

	names := []string{}

	for _, e := range entries {
		if e.IsDir() {
			names = append(names, e.Name())
		}
	}

	sort.Sort(sort.Reverse(sort.StringSlice(names)))
	for _, name := range names {
		abs, err := filepath.Abs(filepath.Join(root, name))
		if err != nil {
			return err
		}
		fmt.Println(abs)
	}
	return nil
}

func createPlaygroundDir() (string, error) {
	root, err := playgroundRootPath()
	if err != nil {
		return "", err
	}

	name := time.Now().Format("2006-01-02_15-04-05")
	path := filepath.Join(root, name)
	path, err = filepath.Abs(path)
	if err != nil {
		return "", err
	}

	if err := os.MkdirAll(path, 0700); err != nil {
		return "", err
	}

	return path, nil
}

func parseConfig() error {
	flag.BoolVar(&config.Edit, "e", false, "Open created script with editor")
	flag.BoolVar(&config.List, "l", false, "List park directries")
	v := flag.Bool("version", false, "print app version")
	flag.Parse()

	if *v {
		fmt.Printf("goplay: %s\n", version)
		os.Exit(0)
	}

	return nil
}

func getenv(name, defaultValue string) string {
	v := os.Getenv(name)
	if v == "" {
		return defaultValue
	}
	return v
}

func getEditorCommand() (string, error) {
	if v := os.Getenv("EDITOR"); v != "" {
		return v, nil
	}

	if v := os.Getenv("VISUAL"); v != "" {
		return v, nil
	}

	// in Unix-like OS, try to find editors
	for _, v := range []string{"editor", "vi", "nano"} {
		_, err := exec.LookPath(v)
		if err != nil {
			continue
		}
		return v, nil
	}
	return "", fmt.Errorf("editor not found, please set $EDITOR environment variable")
}

func createGoFile(dirPath string) (string, error) {
	path, err := filepath.Abs(filepath.Join(dirPath, "main.go"))
	if err != nil {
		return "", err
	}

	if err := ioutil.WriteFile(path, sourceTemplate, 0600); err != nil {
		return "", err
	}
	return path, err
}

func gotoPlayground(dirPath, filePath string) error {
	shell, err := exec.LookPath(getenv("SHELL", "/bin/sh"))
	if err != nil {
		return err
	}

	if err := os.Chdir(dirPath); err != nil {
		return err
	}

	fmt.Fprintf(os.Stderr, "\tcd %s\n", dirPath)

	var argv []string
	if config.Edit {
		editor, err := getEditorCommand()
		if err != nil {
			return err
		}

		fmt.Fprintf(os.Stderr, "\t%s %s\n", editor, filePath)
		argv = []string{
			shell,
			"-c",
			fmt.Sprintf("%s %s; exec %s", editor, filePath, shell),
		}
	} else {
		argv = []string{shell}
	}

	libs, err := filepath.Abs(filepath.Join(dirPath, "golibs"))
	if err != nil {
		return err
	}

	if err := os.Setenv("GOPATH", libs+":"+os.Getenv("GOPATH")); err != nil {
		return err
	}

	return syscall.Exec(shell, argv, syscall.Environ())
}

func main() {
	abortOnError := func(err error) {
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}

	err := parseConfig()
	abortOnError(err)

	if config.List {
		err = listPlaygroundDirs()
		abortOnError(err)
		os.Exit(0)
	}

	dirPath, err := createPlaygroundDir()
	abortOnError(err)

	filePath, err := createGoFile(dirPath)
	abortOnError(err)

	err = gotoPlayground(dirPath, filePath)
	abortOnError(err)

	os.Exit(0)
}
<|endoftext|>


<commit_before>package email

import (
	"errors"
	"github.com/davidbanham/notify/config"
	"github.com/davidbanham/notify/email/amazon"
	"github.com/davidbanham/notify/email/gmail"
	"github.com/davidbanham/notify/email/mandrill"
	"github.com/davidbanham/notify/types"
	"log"
)

var sender func(types.Email) error

func init() {
	provider := config.EmailProvider

	providers := map[string]bool{
		"gmail":    true,
		"mandrill": true,
		"amazon":   true,
		"none":     true,
		"test":     true,
	}

	if !providers[provider] {
		log.Fatal("Invalid email provider specified ", provider, " valid providers are ", providers)
	}

	switch provider {
	case "gmail":
		sender = gmail.Send
		return
	case "mandrill":
		sender = mandrill.Send
		return
	case "amazon":
		sender = amazon.SendFactory(amazon.Init())
		return
	case "test":
		sender = test
		return
	default:
		sender = invalid
		return
	}
}

func invalid(e types.Email) error {
	return errors.New("No valid email provider configured")
}

func test(e types.Email) error {
	return nil
}

// Send an email via the configured provider
func Send(e types.Email) (types.Email, error) {
	if e.From.Name == "" {
		e.From.Name = config.EmailFromName
	}

	if e.From.Address == "" {
		e.From.Address = config.EmailFrom
	}

	return e, sender(e)
}
<commit_msg>Improve test email route<commit_after>package email

import (
	"errors"
	"fmt"
	"github.com/davidbanham/notify/config"
	"github.com/davidbanham/notify/email/amazon"
	"github.com/davidbanham/notify/email/gmail"
	"github.com/davidbanham/notify/email/mandrill"
	"github.com/davidbanham/notify/types"
	"log"
)

var sender func(types.Email) error

func init() {
	provider := config.EmailProvider

	providers := map[string]bool{
		"gmail":    true,
		"mandrill": true,
		"amazon":   true,
		"none":     true,
		"test":     true,
	}

	if !providers[provider] {
		log.Fatal("Invalid email provider specified ", provider, " valid providers are ", providers)
	}

	switch provider {
	case "gmail":
		sender = gmail.Send
		return
	case "mandrill":
		sender = mandrill.Send
		return
	case "amazon":
		sender = amazon.SendFactory(amazon.Init())
		return
	case "test":
		sender = test
		return
	default:
		sender = invalid
		return
	}
}

func invalid(e types.Email) error {
	return errors.New("No valid email provider configured")
}

func test(e types.Email) error {
	fmt.Println("Subject", e.Subject)
	fmt.Println("From", e.From)
	fmt.Println("To", e.To)
	fmt.Println("Body", e.Body)
	fmt.Println("Recieved email. Dropping it due to test route being configured.")
	if e.To.Address == "" {
		return errors.New("No address given to send email to")
	}
	return nil
}

// Send an email via the configured provider
func Send(e types.Email) (types.Email, error) {
	if e.From.Name == "" {
		e.From.Name = config.EmailFromName
	}

	if e.From.Address == "" {
		e.From.Address = config.EmailFrom
	}

	return e, sender(e)
}
<|endoftext|>


<commit_before>package main

import (
  "github.com/fhs/gompd/mpd"
  "github.com/ascherkus/go-id3/src/id3"
  "os"
  "log"
  "encoding/json"
  "fmt"
  "strconv"
  "time"
  //"github.com/kylelemons/go-gypsy/yaml"
  //"os/exec"
  "math/rand"
)

type MusicFile struct {
  Name string
  Artist string
  Album string
}

func main() {
  //testClient()
  testWatcher()

  /*
  config, err := yaml.ReadFile("config.yaml")
  if err != nil {
    log.Fatal("Cannot read config file")
  }

  tbdir, err := config.Get("turbo_wookie_directory")
  if err != nil {
    log.Fatal("No key 'turbo_wookie_directory'.", err)
  }

  mpddir, err := config.Get("mpd_subdirectory")
  if err != nil {
    log.Fatal("No key 'mpd_subdirectory'.", err)
  }

  log.Println("MPD Starting!")
  cmd := exec.Command("mpd", tbdir + mpddir + "/mpd.conf")

  err = cmd.Run()

  time.Sleep(3 * time.Minute)

  if err != nil {
    log.Fatal("Could not start MPD Server!\n", err)
  }

  //defer stopMPD(cmd.Process)
  */

}

func jsoniffy(v interface {}) string {
  obj, _ := json.MarshalIndent(v, "", "  ")
  return string(obj)
}

func testClient() {
  client := clientConnect("localhost:6600")
  defer client.Close()

  upcoming(client)

}

func clientConnect(addr string) *mpd.Client {
  client, err := mpd.Dial("tcp", addr)
  if err != nil {
    return nil
  }

  return client
}

func listSongs(client *mpd.Client) {
  files, _ := client.GetFiles()

  // TODO: grab this from a config.yaml file
  const music_dir string = "mpd/music/"

  for _, song := range files {
    f, err := os.Open(music_dir + song)
    if err != nil {
      log.Fatal(err)
      break
    }

    id3_file := id3.Read(f)

    //log.Printf("%s by %s", id3_file.Name, id3_file.Artist)
    //mfile := MusicFile{id3_file.Name, id3_file.Artist, id3_file.}

    obj, _ := json.Marshal(id3_file)
    log.Print(string(obj))
  }
}

func getCurrentSong(client *mpd.Client) {
  csong, _ := client.CurrentSong()
  obj, _ := json.MarshalIndent(csong, "", "  ")
  fmt.Print(string(obj))
}

func upcoming(client *mpd.Client) {
  csong, _ := client.CurrentSong()
  pos, _ := strconv.Atoi(csong["Pos"])

  playlist, _ := client.PlaylistInfo(-1, -1)
  upcoming := playlist[pos:]

  fmt.Print(jsoniffy(upcoming))
}

///////////////////

func testWatcher() {
  w, _ := mpd.NewWatcher("tcp", ":6600", "")
  defer w.Close()

  go logWatcherErrors(w)
  go logWatcherEvents(w)

  time.Sleep(3 * time.Minute)
}

func logWatcherErrors(w *mpd.Watcher) {
  for err := range w.Error {
    log.Println("Error:", err)
  }
}

func logWatcherEvents(w *mpd.Watcher) {
  for subsystem := range w.Event {
    log.Println("Changed subsystem:", subsystem)

    if subsystem == "player" {
      client := clientConnect("localhost:6600")
      attrs, err := client.Status()
      if err != nil {
        log.Fatal("Couldn't get status...", err)
      }

      if attrs["state"] != "play" {
        for k, v := range attrs {
          fmt.Println("attrs[" + k + "] = " + v)
        }

        songs, err := client.GetFiles()
        if err != nil {
          log.Fatal("Couldn't get files...", err)
        }

        song := songs[random(0, len(songs))]
        if client.Add(song) != nil {
          log.Fatal("Couldn't add song:", song)
        }

        plen, err := strconv.Atoi(attrs["playlistlength"])
        if err != nil {
          log.Fatal("Couldn't get playlistlength...", err)
        }

        if client.Play(plen) != nil {
          log.Fatal("Couldn't play song")
        }
      }

    }
  }
}

func random(min, max int) int {
    rand.Seed(time.Now().Unix())
    return rand.Intn(max - min) + min
}<commit_msg>close client when done with it<commit_after>package main

import (
  "github.com/fhs/gompd/mpd"
  "github.com/ascherkus/go-id3/src/id3"
  "os"
  "log"
  "encoding/json"
  "fmt"
  "strconv"
  "time"
  //"github.com/kylelemons/go-gypsy/yaml"
  //"os/exec"
  "math/rand"
)

type MusicFile struct {
  Name string
  Artist string
  Album string
}

func main() {
  //testClient()
  testWatcher()

  /*
  config, err := yaml.ReadFile("config.yaml")
  if err != nil {
    log.Fatal("Cannot read config file")
  }

  tbdir, err := config.Get("turbo_wookie_directory")
  if err != nil {
    log.Fatal("No key 'turbo_wookie_directory'.", err)
  }

  mpddir, err := config.Get("mpd_subdirectory")
  if err != nil {
    log.Fatal("No key 'mpd_subdirectory'.", err)
  }

  log.Println("MPD Starting!")
  cmd := exec.Command("mpd", tbdir + mpddir + "/mpd.conf")

  err = cmd.Run()

  time.Sleep(3 * time.Minute)

  if err != nil {
    log.Fatal("Could not start MPD Server!\n", err)
  }

  //defer stopMPD(cmd.Process)
  */

}

func jsoniffy(v interface {}) string {
  obj, _ := json.MarshalIndent(v, "", "  ")
  return string(obj)
}

func testClient() {
  client := clientConnect("localhost:6600")
  defer client.Close()

  upcoming(client)

}

func clientConnect(addr string) *mpd.Client {
  client, err := mpd.Dial("tcp", addr)
  if err != nil {
    return nil
  }

  return client
}

func listSongs(client *mpd.Client) {
  files, _ := client.GetFiles()

  // TODO: grab this from a config.yaml file
  const music_dir string = "mpd/music/"

  for _, song := range files {
    f, err := os.Open(music_dir + song)
    if err != nil {
      log.Fatal(err)
      break
    }

    id3_file := id3.Read(f)

    //log.Printf("%s by %s", id3_file.Name, id3_file.Artist)
    //mfile := MusicFile{id3_file.Name, id3_file.Artist, id3_file.}

    obj, _ := json.Marshal(id3_file)
    log.Print(string(obj))
  }
}

func getCurrentSong(client *mpd.Client) {
  csong, _ := client.CurrentSong()
  obj, _ := json.MarshalIndent(csong, "", "  ")
  fmt.Print(string(obj))
}

func upcoming(client *mpd.Client) {
  csong, _ := client.CurrentSong()
  pos, _ := strconv.Atoi(csong["Pos"])

  playlist, _ := client.PlaylistInfo(-1, -1)
  upcoming := playlist[pos:]

  fmt.Print(jsoniffy(upcoming))
}

///////////////////

func testWatcher() {
  w, _ := mpd.NewWatcher("tcp", ":6600", "")
  defer w.Close()

  go logWatcherErrors(w)
  go logWatcherEvents(w)

  time.Sleep(3 * time.Minute)
}

func logWatcherErrors(w *mpd.Watcher) {
  for err := range w.Error {
    log.Println("Error:", err)
  }
}

func logWatcherEvents(w *mpd.Watcher) {
  for subsystem := range w.Event {
    log.Println("Changed subsystem:", subsystem)

    if subsystem == "player" {
      client := clientConnect("localhost:6600")
      attrs, err := client.Status()
      if err != nil {
        log.Fatal("Couldn't get status...", err)
      }

      if attrs["state"] != "play" {
        for k, v := range attrs {
          fmt.Println("attrs[" + k + "] = " + v)
        }

        songs, err := client.GetFiles()
        if err != nil {
          log.Fatal("Couldn't get files...", err)
        }

        song := songs[random(0, len(songs))]
        if client.Add(song) != nil {
          log.Fatal("Couldn't add song:", song)
        }

        plen, err := strconv.Atoi(attrs["playlistlength"])
        if err != nil {
          log.Fatal("Couldn't get playlistlength...", err)
        }

        if client.Play(plen) != nil {
          log.Fatal("Couldn't play song")
        }
      }

      client.Close()
    }
  }
}

func random(min, max int) int {
    rand.Seed(time.Now().Unix())
    return rand.Intn(max - min) + min
}<|endoftext|>


<commit_before>package fezzik_test

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"regexp"
	"sync"
	"sync/atomic"

	"github.com/onsi/gomega/ghttp"

	"github.com/cloudfoundry/gunk/workpool"

	. "github.com/cloudfoundry-incubator/fezzik"
	"github.com/cloudfoundry-incubator/receptor"
	"github.com/cloudfoundry-incubator/runtime-schema/models"

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

func NewLightweightTask(guid string, addr string) receptor.TaskCreateRequest {
	return receptor.TaskCreateRequest{
		TaskGuid: guid,
		Domain:   domain,
		Stack:    stack,
		Action: &models.RunAction{
			Path: "bash",
			Args: []string{"-c", fmt.Sprintf("echo '%s' > /tmp/output", guid)},
		},
		CompletionCallbackURL: fmt.Sprintf("http://%s/done", addr),
		DiskMB:                64,
		MemoryMB:              64,
		ResultFile:            "/tmp/output",
	}
}

func TasksByDomainFetcher(domain string) func() ([]receptor.TaskResponse, error) {
	return func() ([]receptor.TaskResponse, error) {
		return client.TasksByDomain(domain)
	}
}

func safeWait(wg *sync.WaitGroup) chan struct{} {
	c := make(chan struct{})

	go func() {
		wg.Wait()
		close(c)
	}()

	return c
}

func NewGHTTPServer() (*ghttp.Server, string) {
	server := ghttp.NewUnstartedServer()
	l, err := net.Listen("tcp", "0.0.0.0:0")
	Ω(err).ShouldNot(HaveOccurred())
	server.HTTPTestServer.Listener = l
	server.HTTPTestServer.Start()

	re := regexp.MustCompile(`:(\d+)$`)
	port := re.FindStringSubmatch(server.URL())[1]
	Ω(port).ShouldNot(BeZero())

	//for bosh-lite only -- need something more sophisticated later.
	return server, fmt.Sprintf("%s:%s", publiclyAccessibleIP, port)
}

var _ = Describe("Running Many Tasks", func() {
	for _, factor := range []int{1, 5, 10, 20, 40} {
		factor := factor

		/*
			Commentary:

			Currently, this test shows a degradation in performance as `factor` increases.
			On Bosh-Lite I've traced this down to degrading Garden performance when many containers are created concurrently.
			This is unsuprising and is likely disk-io bound.  None of the degredation appears to be related to Diego's scheduling however.
		*/

		Context("when the tasks are lightweight (no downloads, no uploads)", func() {
			var workPool *workpool.WorkPool
			var tasks []receptor.TaskCreateRequest
			var taskReporter *TaskReporter
			var server *ghttp.Server

			BeforeEach(func() {
				var addr string

				workPool = workpool.NewWorkPool(500)
				server, addr = NewGHTTPServer()

				tasks = []receptor.TaskCreateRequest{}
				guid := NewGuid()
				for i := 0; i < factor*numCells; i++ {
					tasks = append(tasks, NewLightweightTask(fmt.Sprintf("%s-%d", guid, i), addr))
				}

				cells, err := client.Cells()
				Ω(err).ShouldNot(HaveOccurred())
				reportName := fmt.Sprintf("Running %d Tasks Across %d Cells", len(tasks), numCells)
				taskReporter = NewTaskReporter(reportName, len(tasks), cells)
			})

			AfterEach(func() {
				workPool.Stop()
				taskReporter.EmitSummary()
				taskReporter.Save()
			})

			It(fmt.Sprintf("should handle numCellx%d concurrent tasks", factor), func() {
				allCompleted := make(chan struct{})
				completionCounter := int64(0)
				server.RouteToHandler("POST", "/done", func(w http.ResponseWriter, req *http.Request) {
					defer func() {
						if atomic.AddInt64(&completionCounter, 1) >= int64(len(tasks)) {
							close(allCompleted)
						}
					}()
					var receivedTask receptor.TaskResponse
					json.NewDecoder(req.Body).Decode(&receivedTask)
					taskReporter.Completed(receivedTask)
				})

				wg := &sync.WaitGroup{}
				wg.Add(len(tasks))
				for _, task := range tasks {
					task := task
					workPool.Submit(func() {
						defer wg.Done()
						err := client.CreateTask(task)
						if err != nil {
							fmt.Println(err.Error())
							return
						}
						taskReporter.DidCreate(task.TaskGuid)
					})
				}

				Eventually(safeWait(wg), 240).Should(BeClosed())
				Eventually(allCompleted, 240).Should(BeClosed())
				Eventually(TasksByDomainFetcher(domain), 240).Should(BeEmpty())
			})
		})
	}
})
<commit_msg>Add egress rules so processes can hit publically accessible IP [#86856658]<commit_after>package fezzik_test

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"regexp"
	"sync"
	"sync/atomic"

	"github.com/onsi/gomega/ghttp"

	"github.com/cloudfoundry/gunk/workpool"

	. "github.com/cloudfoundry-incubator/fezzik"
	"github.com/cloudfoundry-incubator/receptor"
	"github.com/cloudfoundry-incubator/runtime-schema/models"

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

func NewLightweightTask(guid string, addr string) receptor.TaskCreateRequest {
	return receptor.TaskCreateRequest{
		TaskGuid: guid,
		Domain:   domain,
		Stack:    stack,
		Action: &models.RunAction{
			Path: "bash",
			Args: []string{"-c", fmt.Sprintf("echo '%s' > /tmp/output", guid)},
		},
		CompletionCallbackURL: fmt.Sprintf("http://%s/done", addr),
		DiskMB:                64,
		MemoryMB:              64,
		EgressRules: []models.SecurityGroupRule{
			{
				Protocol:     models.AllProtocol,
				Destinations: []string{"0.0.0.0/0"},
			},
		},
		ResultFile: "/tmp/output",
	}
}

func TasksByDomainFetcher(domain string) func() ([]receptor.TaskResponse, error) {
	return func() ([]receptor.TaskResponse, error) {
		return client.TasksByDomain(domain)
	}
}

func safeWait(wg *sync.WaitGroup) chan struct{} {
	c := make(chan struct{})

	go func() {
		wg.Wait()
		close(c)
	}()

	return c
}

func NewGHTTPServer() (*ghttp.Server, string) {
	server := ghttp.NewUnstartedServer()
	l, err := net.Listen("tcp", "0.0.0.0:0")
	Ω(err).ShouldNot(HaveOccurred())
	server.HTTPTestServer.Listener = l
	server.HTTPTestServer.Start()

	re := regexp.MustCompile(`:(\d+)$`)
	port := re.FindStringSubmatch(server.URL())[1]
	Ω(port).ShouldNot(BeZero())

	//for bosh-lite only -- need something more sophisticated later.
	return server, fmt.Sprintf("%s:%s", publiclyAccessibleIP, port)
}

var _ = Describe("Running Many Tasks", func() {
	for _, factor := range []int{1, 5, 10, 20, 40} {
		factor := factor

		/*
			Commentary:

			Currently, this test shows a degradation in performance as `factor` increases.
			On Bosh-Lite I've traced this down to degrading Garden performance when many containers are created concurrently.
			This is unsuprising and is likely disk-io bound.  None of the degredation appears to be related to Diego's scheduling however.
		*/

		Context("when the tasks are lightweight (no downloads, no uploads)", func() {
			var workPool *workpool.WorkPool
			var tasks []receptor.TaskCreateRequest
			var taskReporter *TaskReporter
			var server *ghttp.Server

			BeforeEach(func() {
				var addr string

				workPool = workpool.NewWorkPool(500)
				server, addr = NewGHTTPServer()

				tasks = []receptor.TaskCreateRequest{}
				guid := NewGuid()
				for i := 0; i < factor*numCells; i++ {
					tasks = append(tasks, NewLightweightTask(fmt.Sprintf("%s-%d", guid, i), addr))
				}

				cells, err := client.Cells()
				Ω(err).ShouldNot(HaveOccurred())
				reportName := fmt.Sprintf("Running %d Tasks Across %d Cells", len(tasks), numCells)
				taskReporter = NewTaskReporter(reportName, len(tasks), cells)
			})

			AfterEach(func() {
				workPool.Stop()
				taskReporter.EmitSummary()
				taskReporter.Save()
			})

			It(fmt.Sprintf("should handle numCellx%d concurrent tasks", factor), func() {
				allCompleted := make(chan struct{})
				completionCounter := int64(0)
				server.RouteToHandler("POST", "/done", func(w http.ResponseWriter, req *http.Request) {
					defer func() {
						if atomic.AddInt64(&completionCounter, 1) >= int64(len(tasks)) {
							close(allCompleted)
						}
					}()
					var receivedTask receptor.TaskResponse
					json.NewDecoder(req.Body).Decode(&receivedTask)
					taskReporter.Completed(receivedTask)
				})

				wg := &sync.WaitGroup{}
				wg.Add(len(tasks))
				for _, task := range tasks {
					task := task
					workPool.Submit(func() {
						defer wg.Done()
						err := client.CreateTask(task)
						if err != nil {
							fmt.Println(err.Error())
							return
						}
						taskReporter.DidCreate(task.TaskGuid)
					})
				}

				Eventually(safeWait(wg), 240).Should(BeClosed())
				Eventually(allCompleted, 240).Should(BeClosed())
				Eventually(TasksByDomainFetcher(domain), 240).Should(BeEmpty())
			})
		})
	}
})
<|endoftext|>


<commit_before>package magicsql_test

import (
	"fmt"
	"github.com/Nerdmaster/magicsql"
	_ "github.com/mattn/go-sqlite3"
)

// Foo demonstrates some of the optional database magic
type Foo struct {
	// ID is the primary key, but not explicitly given a field name, so it'll be "id"
	ID int `sql:",primary"`
	// ONE turns into "one" for field name, as we auto-lowercase anything not tagged
	ONE string
	// TwO just shows that the field's case is lowercased even when it may have
	// been camelcase in the structure
	TwO int
	// Three is explicitly set to "tree"
	Three bool `sql:"tree"`
	// Four is just lowercased to "four"
	Four int
	// Five is explicitly skipped
	Five int `sql:"-"`
	// six isn't exported, so is implicitly skipped
	six string
}

// newFoo is the generator for creating a default Foo instance
func newFoo() interface{} {
	return &Foo{Five: 5, six: "six"}
}

// Example_withMagic showcases some of the ways SQL can be magically generated
// to populate registered structures
func Example_withMagic() {
	// Set up a simple sqlite database
	var db, err = magicsql.Open("sqlite3", "./test.db")
	if err != nil {
		panic(err)
	}

	var sqlStmt = `
		DROP TABLE IF EXISTS foos;
		CREATE TABLE foos (
			id   INTEGER NOT NULL PRIMARY KEY,
			one  TEXT,
			two  INT,
			tree BOOL,
			four INT
		);
		INSERT INTO foos (one,two,tree,four) VALUES ("one", 2, 1, 4);
		INSERT INTO foos (one,two,tree,four) VALUES ("thing", 5, 0, 7);
		INSERT INTO foos (one,two,tree,four) VALUES ("blargh", 1, 1, 5);
		INSERT INTO foos (one,two,tree,four) VALUES ("sploop", 2, 1, 4);
	`

	_, err = db.DataSource().Exec(sqlStmt)
	if err != nil {
		panic(err)
	}

	// Tie the "foos" table to the Foo type
	db.RegisterTable("foos", newFoo)
	var op = db.Operation()

	var fooList []*Foo
	op.From("foos").Where("two > 1").Limit(2).SelectAllInto(&fooList)

	for _, f := range fooList {
		fmt.Printf("Foo {%d,%s,%d,%#v,%d,%d,%s}\n", f.ID, f.ONE, f.TwO, f.Three, f.Four, f.Five, f.six)
	}
	// Output:
	// Foo {1,one,2,true,4,5,six}
	// Foo {2,thing,5,false,7,5,six}
}
<commit_msg>Demonstrate more of the magic in the magic example<commit_after>package magicsql_test

import (
	"fmt"
	"github.com/Nerdmaster/magicsql"
	_ "github.com/mattn/go-sqlite3"
)

// Foo demonstrates some of the optional database magic
type Foo struct {
	// ID is the primary key, but not explicitly given a field name, so it'll be "id"
	ID int `sql:",primary"`
	// ONE turns into "one" for field name, as we auto-lowercase anything not tagged
	ONE string
	// TwO just shows that the field's case is lowercased even when it may have
	// been camelcase in the structure
	TwO int
	// Three is explicitly set to "tree"
	Three bool `sql:"tree"`
	// Four is just lowercased to "four"
	Four int
	// Five is explicitly skipped
	Five int `sql:"-"`
	// six isn't exported, so is implicitly skipped
	six string
}

// newFoo is the generator for creating a default Foo instance
func newFoo() interface{} {
	return &Foo{Five: 5, six: "six"}
}

// Example_withMagic showcases some of the ways SQL can be magically generated
// to populate registered structures
func Example_withMagic() {
	// Set up a simple sqlite database
	var db, err = magicsql.Open("sqlite3", "./test.db")
	if err != nil {
		panic(err)
	}

	// Tie the "foos" table to the Foo type
	db.RegisterTable("foos", newFoo)
	var op = db.Operation()

	// Create table schema
	op.Exec("DROP TABLE IF EXISTS foos")
	op.Exec("CREATE TABLE foos (id INTEGER NOT NULL PRIMARY KEY, one TEXT, two INT, tree BOOL, four INT)")

	// Insert four rows
	op.BeginTransaction()
	op.Save(&Foo{ONE: "one", TwO: 2, Three: true, Four: 4})
	op.Save(&Foo{ONE: "thing", TwO: 5, Three: false, Four: 7})
	op.Save(&Foo{ONE: "blargh", TwO: 1, Three: true, Four: 5})
	op.Save(&Foo{ONE: "sploop", TwO: 2, Three: true, Four: 4})
	op.EndTransaction()
	if op.Err() != nil {
		panic(op.Err())
	}

	var fooList []*Foo
	op.From("foos").Where("two > 1").Limit(2).Offset(1).SelectAllInto(&fooList)

	for _, f := range fooList {
		fmt.Printf("Foo {%d,%s,%d,%#v,%d,%d,%s}\n", f.ID, f.ONE, f.TwO, f.Three, f.Four, f.Five, f.six)
	}
	// Output:
	// Foo {2,thing,5,false,7,5,six}
	// Foo {4,sploop,2,true,4,5,six}
}
<|endoftext|>


<commit_before>package target

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	yaml "gopkg.in/yaml.v2"

	fi "github.com/upsight/ron/file"
	template "github.com/upsight/ron/template"
)

const (
	// ConfigDirName is the name of the folder where ron will look for yaml config
	// files.
	ConfigDirName = ".ron"
	// ConfigFileName is the main ron config file that overrides other files.
	ConfigFileName = "ron.yaml"
)

// ConfigFile is used to unmarshal configuration files.
type ConfigFile struct {
	Envs    []map[string]string `json:"envs" yaml:"envs"`
	Targets map[string]struct {
		Before      []string `json:"before" yaml:"before"`
		After       []string `json:"after" yaml:"after"`
		Cmd         string   `json:"cmd" yaml:"cmd"`
		Description string   `json:"description" yaml:"description"`
	} `json:"targets" yaml:"targets"`
}

// EnvsString is used for debugging the loaded envs.
func (c *ConfigFile) EnvsString() string {
	envs, _ := yaml.Marshal(c.Envs)
	return string(envs)
}

// TargetsString is used for debugging the loaded targets.
func (c *ConfigFile) TargetsString() string {
	targets, _ := yaml.Marshal(c.Targets)
	return string(targets)
}

// RawConfig contains the raw strings from a loaded config file.
type RawConfig struct {
	Filepath string
	Envs     string
	Targets  string
}

// extractConfigError parses the error for line number and then
// generates the text surrounding it.
func extractConfigError(path, input string, inErr error) error {
	err := inErr
	re := regexp.MustCompile(`line ([0-9]+):.*$`)
	v := re.FindStringSubmatch(inErr.Error())
	if v != nil && len(v) > 1 {
		if lineNum, e := strconv.Atoi(v[1]); e == nil {
			text := []string{}
			inLines := strings.Split(input, "\n")
			between := [2]int{lineNum - 5, lineNum + 5}
			for i, line := range inLines {
				switch {
				case i+1 == lineNum:
					text = append(text, line+" <<<<<<<<<<")
				case i+1 > between[0] && i+1 < between[1]:
					text = append(text, line)
				}
			}
			err = fmt.Errorf("%s %s\n%s ", path, inErr.Error(), strings.Join(text, "\n"))
		}
	}
	return err
}

// findConfigFile will search for a ron.yaml file, starting with the current directory
// and then searching parent directories for a first occurrence.
func findConfigFile() (string, error) {
	dir, err := os.Getwd()
	if err != nil {
		return "", err
	}
	for {
		fp := filepath.Join(dir, ConfigFileName)
		if _, err := os.Stat(fp); err == nil {
			return fp, nil
		} else if !os.IsNotExist(err) {
			return "", err
		}
		parentDir := filepath.Dir(dir)
		if parentDir == dir {
			return "", nil
		}
		dir = parentDir
	}
}

// findConfigDirs will search in the current directory for a .ron folder
// with *.yaml files, and then search parent directories.
func findConfigDirs(curdir string) (dirs []string, err error) {
	defer func() {
		// append the users home directory before returning
		hd := filepath.Join(homeDir(), ConfigDirName)
		if _, err := os.Stat(hd); err == nil {
			dirs = append(dirs, hd)
		}
	}()

	for {
		dirpath := filepath.Join(curdir, ConfigDirName)
		if _, err = os.Stat(dirpath); err == nil {
			dirs = append(dirs, dirpath)
			return
		}
		parentDir := filepath.Dir(curdir)
		if parentDir == curdir {
			return
		}
		curdir = parentDir
	}
}

// findConfigDirFiles will find any *.yaml files in a list of .ron directories.
func findConfigDirFiles(dirs []string) (files []string, err error) {
	for _, dir := range dirs {
		found, err := filepath.Glob(filepath.Join(dir, "*.yaml"))
		if err == nil {
			files = append(files, found...)
		}
	}
	return
}

// addRonDirConfigs will first find any .ron folders in the current
// directory, followed by appending the home directory .ron folder.
// Any errors here will abort adding conigs and just return.
func addRonDirConfigs(wd string, configs *[]*RawConfig) {
	dirs, err := findConfigDirs(wd)
	if err != nil {
		return
	}
	files, err := findConfigDirFiles(dirs)
	if err != nil {
		return
	}
	for _, file := range files {
		conf, err := LoadConfigFile(file)
		if err != nil {
			continue
		}
		*configs = append(*configs, conf)
	}
}

// addRonYamlFile will prepend the list of configs with
// any ron.yaml files that are found along with returning its location.
func addRonYamlFile(overrideYamlPath string, configs *[]*RawConfig) (string, error) {
	var err error
	foundConfigDir := ""
	if overrideYamlPath == "" {
		overrideYamlPath, err = findConfigFile()
		if err != nil {
			return "", err
		}
		foundConfigDir = filepath.Dir(overrideYamlPath)
	}

	if overrideYamlPath != "" {
		overrideConfig, err := LoadConfigFile(overrideYamlPath)
		if err != nil {
			return "", err
		}
		overrideConfig.Filepath = overrideYamlPath
		overrideConfig.Envs = strings.TrimSpace(overrideConfig.Envs)
		overrideConfig.Targets = strings.TrimSpace(overrideConfig.Targets)
		// prepend the override config
		*configs = append([]*RawConfig{overrideConfig}, *configs...)
	}

	return foundConfigDir, err
}

// addDefaultYamlFile will add a default config which should always be
// last in priority. If no path option is given a built in default will
// be created.
func addDefaultYamlFile(defaultYamlPath string, configs *[]*RawConfig) {
	envs, targets, err := BuiltinDefault()
	defaultConfig := &RawConfig{
		Filepath: "builtin:target/default.yaml",
		Envs:     envs,
		Targets:  targets,
	}
	if defaultYamlPath != "" {
		defaultConfig, err = LoadConfigFile(defaultYamlPath)
		if err != nil {
			return
		}
		defaultConfig.Filepath = defaultYamlPath
	}
	defaultConfig.Envs = strings.TrimSpace(defaultConfig.Envs)
	defaultConfig.Targets = strings.TrimSpace(defaultConfig.Targets)
	*configs = append(*configs, defaultConfig)
}

// LoadConfigFiles loads the default, override, and any directory config files
// and returns them as a slice. If defaultYamlPath is an empty string, the defaults
// compiled into ron will be used instead. If overrideYamlPath is blank,
// it will find the nearest parent folder containing a ron.yaml file and use
// that file instead. In that case, the path to that file will be returned
// so that the caller can change the working directory to that folder before
// running further commands.
func LoadConfigFiles(defaultYamlPath, overrideYamlPath string) ([]*RawConfig, string, error) {
	configs := []*RawConfig{}

	foundConfigDir, err := addRonYamlFile(overrideYamlPath, &configs)

	wd, err := os.Getwd()
	if err == nil {
		addRonDirConfigs(wd, &configs)
	}
	addDefaultYamlFile(defaultYamlPath, &configs)
	return configs, foundConfigDir, nil
}

// LoadConfigFile will open a given file path and return it's raw
// envs and targets.
var LoadConfigFile = func(path string) (*RawConfig, error) {
	path, err := filepath.Abs(path)
	if err != nil {
		return nil, err
	}
	f, err := fi.NewFile(path)
	if err != nil {
		return nil, err
	}
	content, err := template.RenderGo(path, f.String())
	if err != nil {
		return nil, err
	}

	var c *ConfigFile
	err = yaml.Unmarshal([]byte(content), &c)
	if err != nil {
		return nil, extractConfigError(path, content, err)
	}
	if c == nil {
		return nil, fmt.Errorf("empty file requires envs and target keys")
	}
	envs, err := yaml.Marshal(c.Envs)
	if err != nil {
		return nil, err
	}
	targets, err := yaml.Marshal(c.Targets)
	if err != nil {
		return nil, err
	}
	return &RawConfig{Filepath: path, Envs: string(envs), Targets: string(targets)}, nil
}

// BuiltinDefault loads the binary yaml file and returns envs, targets, and any errors.
func BuiltinDefault() (string, string, error) {
	defaultYaml, err := Asset("target/default.yaml")
	if err != nil {
		return "", "", err
	}
	content, err := template.RenderGo("builtin:target/default.yaml", string(defaultYaml))
	if err != nil {
		return "", "", err
	}

	var c *ConfigFile
	err = yaml.Unmarshal([]byte(content), &c)
	if err != nil {
		return "", "", err
	}

	// load envs
	d, err := yaml.Marshal(c.Envs)
	if err != nil {
		return "", "", err
	}
	envs := string(d)

	// load targets
	d, err = yaml.Marshal(c.Targets)
	if err != nil {
		return "", "", err
	}
	targets := string(d)

	return envs, targets, nil
}
<commit_msg>Adds logging for errors on invalid config files<commit_after>package target

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	yaml "gopkg.in/yaml.v2"

	"github.com/upsight/ron/color"
	fi "github.com/upsight/ron/file"
	template "github.com/upsight/ron/template"
)

const (
	// ConfigDirName is the name of the folder where ron will look for yaml config
	// files.
	ConfigDirName = ".ron"
	// ConfigFileName is the main ron config file that overrides other files.
	ConfigFileName = "ron.yaml"
)

// ConfigFile is used to unmarshal configuration files.
type ConfigFile struct {
	Envs    []map[string]string `json:"envs" yaml:"envs"`
	Targets map[string]struct {
		Before      []string `json:"before" yaml:"before"`
		After       []string `json:"after" yaml:"after"`
		Cmd         string   `json:"cmd" yaml:"cmd"`
		Description string   `json:"description" yaml:"description"`
	} `json:"targets" yaml:"targets"`
}

// EnvsString is used for debugging the loaded envs.
func (c *ConfigFile) EnvsString() string {
	envs, _ := yaml.Marshal(c.Envs)
	return string(envs)
}

// TargetsString is used for debugging the loaded targets.
func (c *ConfigFile) TargetsString() string {
	targets, _ := yaml.Marshal(c.Targets)
	return string(targets)
}

// RawConfig contains the raw strings from a loaded config file.
type RawConfig struct {
	Filepath string
	Envs     string
	Targets  string
}

// extractConfigError parses the error for line number and then
// generates the text surrounding it.
func extractConfigError(path, input string, inErr error) error {
	err := inErr
	re := regexp.MustCompile(`line ([0-9]+):.*$`)
	v := re.FindStringSubmatch(inErr.Error())
	if v != nil && len(v) > 1 {
		if lineNum, e := strconv.Atoi(v[1]); e == nil {
			text := []string{}
			inLines := strings.Split(input, "\n")
			between := [2]int{lineNum - 5, lineNum + 5}
			for i, line := range inLines {
				switch {
				case i+1 == lineNum:
					text = append(text, line+" <<<<<<<<<<")
				case i+1 > between[0] && i+1 < between[1]:
					text = append(text, line)
				}
			}
			err = fmt.Errorf("%s %s\n%s ", path, inErr.Error(), strings.Join(text, "\n"))
		}
	}
	return err
}

// findConfigFile will search for a ron.yaml file, starting with the current directory
// and then searching parent directories for a first occurrence.
func findConfigFile() (string, error) {
	dir, err := os.Getwd()
	if err != nil {
		return "", err
	}
	for {
		fp := filepath.Join(dir, ConfigFileName)
		if _, err := os.Stat(fp); err == nil {
			return fp, nil
		} else if !os.IsNotExist(err) {
			return "", err
		}
		parentDir := filepath.Dir(dir)
		if parentDir == dir {
			return "", nil
		}
		dir = parentDir
	}
}

// findConfigDirs will search in the current directory for a .ron folder
// with *.yaml files, and then search parent directories.
func findConfigDirs(curdir string) (dirs []string, err error) {
	defer func() {
		// append the users home directory before returning
		hd := filepath.Join(homeDir(), ConfigDirName)
		if _, err := os.Stat(hd); err == nil {
			dirs = append(dirs, hd)
		}
	}()

	for {
		dirpath := filepath.Join(curdir, ConfigDirName)
		if _, err = os.Stat(dirpath); err == nil {
			dirs = append(dirs, dirpath)
			return
		}
		parentDir := filepath.Dir(curdir)
		if parentDir == curdir {
			return
		}
		curdir = parentDir
	}
}

// findConfigDirFiles will find any *.yaml files in a list of .ron directories.
func findConfigDirFiles(dirs []string) (files []string, err error) {
	for _, dir := range dirs {
		found, err := filepath.Glob(filepath.Join(dir, "*.yaml"))
		if err == nil {
			files = append(files, found...)
		}
	}
	return
}

// addRonDirConfigs will first find any .ron folders in the current
// directory, followed by appending the home directory .ron folder.
// Any errors here will abort adding conigs and just return.
func addRonDirConfigs(wd string, configs *[]*RawConfig) {
	dirs, err := findConfigDirs(wd)
	if err != nil {
		fmt.Println(color.Red(err.Error()))
		return
	}
	files, err := findConfigDirFiles(dirs)
	if err != nil {
		fmt.Println(color.Red(err.Error()))
		return
	}
	for _, file := range files {
		conf, err := LoadConfigFile(file)
		if err != nil {
			fmt.Println(color.Red(err.Error()))
			continue
		}
		*configs = append(*configs, conf)
	}
}

// addRonYamlFile will prepend the list of configs with
// any ron.yaml files that are found along with returning its location.
func addRonYamlFile(overrideYamlPath string, configs *[]*RawConfig) (string, error) {
	var err error
	foundConfigDir := ""
	if overrideYamlPath == "" {
		overrideYamlPath, err = findConfigFile()
		if err != nil {
			fmt.Println(color.Red(err.Error()))
			return "", err
		}
		foundConfigDir = filepath.Dir(overrideYamlPath)
	}

	if overrideYamlPath != "" {
		overrideConfig, err := LoadConfigFile(overrideYamlPath)
		if err != nil {
			fmt.Println(color.Red(err.Error()))
			return "", err
		}
		overrideConfig.Filepath = overrideYamlPath
		overrideConfig.Envs = strings.TrimSpace(overrideConfig.Envs)
		overrideConfig.Targets = strings.TrimSpace(overrideConfig.Targets)
		// prepend the override config
		*configs = append([]*RawConfig{overrideConfig}, *configs...)
	}

	return foundConfigDir, err
}

// addDefaultYamlFile will add a default config which should always be
// last in priority. If no path option is given a built in default will
// be created.
func addDefaultYamlFile(defaultYamlPath string, configs *[]*RawConfig) {
	envs, targets, err := BuiltinDefault()
	defaultConfig := &RawConfig{
		Filepath: "builtin:target/default.yaml",
		Envs:     envs,
		Targets:  targets,
	}
	if defaultYamlPath != "" {
		defaultConfig, err = LoadConfigFile(defaultYamlPath)
		if err != nil {
			fmt.Println(color.Red(err.Error()))
			return
		}
		defaultConfig.Filepath = defaultYamlPath
	}
	defaultConfig.Envs = strings.TrimSpace(defaultConfig.Envs)
	defaultConfig.Targets = strings.TrimSpace(defaultConfig.Targets)
	*configs = append(*configs, defaultConfig)
}

// LoadConfigFiles loads the default, override, and any directory config files
// and returns them as a slice. If defaultYamlPath is an empty string, the defaults
// compiled into ron will be used instead. If overrideYamlPath is blank,
// it will find the nearest parent folder containing a ron.yaml file and use
// that file instead. In that case, the path to that file will be returned
// so that the caller can change the working directory to that folder before
// running further commands.
func LoadConfigFiles(defaultYamlPath, overrideYamlPath string) ([]*RawConfig, string, error) {
	configs := []*RawConfig{}

	foundConfigDir, err := addRonYamlFile(overrideYamlPath, &configs)

	wd, err := os.Getwd()
	if err == nil {
		addRonDirConfigs(wd, &configs)
	}
	addDefaultYamlFile(defaultYamlPath, &configs)
	return configs, foundConfigDir, nil
}

// LoadConfigFile will open a given file path and return it's raw
// envs and targets.
var LoadConfigFile = func(path string) (*RawConfig, error) {
	path, err := filepath.Abs(path)
	if err != nil {
		return nil, err
	}
	f, err := fi.NewFile(path)
	if err != nil {
		return nil, err
	}
	content, err := template.RenderGo(path, f.String())
	if err != nil {
		return nil, err
	}

	var c *ConfigFile
	err = yaml.Unmarshal([]byte(content), &c)
	if err != nil {
		return nil, extractConfigError(path, content, err)
	}
	if c == nil {
		return nil, fmt.Errorf("empty file requires envs and target keys")
	}
	envs, err := yaml.Marshal(c.Envs)
	if err != nil {
		return nil, err
	}
	targets, err := yaml.Marshal(c.Targets)
	if err != nil {
		return nil, err
	}
	return &RawConfig{Filepath: path, Envs: string(envs), Targets: string(targets)}, nil
}

// BuiltinDefault loads the binary yaml file and returns envs, targets, and any errors.
func BuiltinDefault() (string, string, error) {
	defaultYaml, err := Asset("target/default.yaml")
	if err != nil {
		return "", "", err
	}
	content, err := template.RenderGo("builtin:target/default.yaml", string(defaultYaml))
	if err != nil {
		return "", "", err
	}

	var c *ConfigFile
	err = yaml.Unmarshal([]byte(content), &c)
	if err != nil {
		return "", "", err
	}

	// load envs
	d, err := yaml.Marshal(c.Envs)
	if err != nil {
		return "", "", err
	}
	envs := string(d)

	// load targets
	d, err = yaml.Marshal(c.Targets)
	if err != nil {
		return "", "", err
	}
	targets := string(d)

	return envs, targets, nil
}
<|endoftext|>


<commit_before>package slackevents

import (
	"encoding/json"
	"github.com/nlopes/slack"
)

type MessageActionResponse struct {
	ResponseType    string `json:"response_type"`
	ReplaceOriginal bool   `json:"replace_original"`
	Text            string `json:"text"`
}

type MessageActionEntity struct {
	Id     string `json:"id"`
	Domain string `json:"domain"`
}

type MessageAction struct {
	Type             string                   `json:"type"`
	Actions          []slack.AttachmentAction `json:"actions"`
	CallbackId       string                   `json:"callback_id"`
	Team             MessageActionEntity      `json:"team"`
	Channel          MessageActionEntity      `json:"channel"`
	User             MessageActionEntity      `json:"user"`
	ActionTimestamp  json.Number              `json:"action_ts"`
	MessageTimestamp json.Number              `json:"message_ts"`
	AttachmentId     json.Number              `json:"attachment_id"`
	Token            string                   `json:"token"`
	OriginalMessage  slack.Message            `json:"message"`
	ResponseUrl      string                   `json:"response_url"`
	TriggerId        string                   `json:"trigger_id"`
}
<commit_msg>support both message and original message<commit_after>package slackevents

import (
	"encoding/json"

	"github.com/nlopes/slack"
)

type MessageActionResponse struct {
	ResponseType    string `json:"response_type"`
	ReplaceOriginal bool   `json:"replace_original"`
	Text            string `json:"text"`
}

type MessageActionEntity struct {
	Id     string `json:"id"`
	Domain string `json:"domain"`
}

type MessageAction struct {
	Type             string                   `json:"type"`
	Actions          []slack.AttachmentAction `json:"actions"`
	CallbackId       string                   `json:"callback_id"`
	Team             MessageActionEntity      `json:"team"`
	Channel          MessageActionEntity      `json:"channel"`
	User             MessageActionEntity      `json:"user"`
	ActionTimestamp  json.Number              `json:"action_ts"`
	MessageTimestamp json.Number              `json:"message_ts"`
	AttachmentId     json.Number              `json:"attachment_id"`
	Token            string                   `json:"token"`
	Message          slack.Message            `json:"message"`
	OriginalMessage  slack.Message            `json:"original_message"`
	ResponseUrl      string                   `json:"response_url"`
	TriggerId        string                   `json:"trigger_id"`
}
<|endoftext|>


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

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"math/rand"
	"sync/atomic"
	"time"

	"github.com/cockroachdb/cockroach/client"
	"github.com/cockroachdb/cockroach/security"
	"github.com/cockroachdb/cockroach/security/securitytest"
	"github.com/cockroachdb/cockroach/server"
	"github.com/cockroachdb/cockroach/util/log"
)

var useTransaction = flag.Bool("use-transaction", true, "Turn off to disable transaction.")

// Makes an id string from an id int.
func makeAccountID(id int) []byte {
	return []byte(fmt.Sprintf("%09d", id))
}

// Bank stores all the bank related state.
type Bank struct {
	db           *client.DB
	numAccounts  int
	numTransfers int32
}

type Account struct {
	Balance int64
}

func (a Account) encode() ([]byte, error) {
	return json.Marshal(a)
}

func (a *Account) decode(b []byte) error {
	return json.Unmarshal(b, a)
}

// Read the balances in all the accounts and return them.
func (bank *Bank) sumAllAccounts() int64 {
	var result int64
	err := bank.db.Tx(func(tx *client.Tx) error {
		scan := tx.Scan(makeAccountID(0), makeAccountID(bank.numAccounts), int64(bank.numAccounts))
		if scan.Err != nil {
			log.Fatal(scan.Err)
		}
		if len(scan.Rows) != bank.numAccounts {
			log.Fatalf("Could only read %d of %d rows of the database.\n", len(scan.Rows), bank.numAccounts)
		}
		// Copy responses into balances.
		for i := 0; i < bank.numAccounts; i++ {
			account := &Account{}
			err := account.decode(scan.Rows[i].ValueBytes())
			if err != nil {
				log.Fatal(err)
			}
			// fmt.Printf("Account %d contains %d$\n", i, account.Balance)
			result += account.Balance
		}
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}
	return result
}

// continuouslyTransferMoney() keeps moving random amounts between
// random accounts.
func (bank *Bank) continuousMoneyTransfer() {
	for {
		from := makeAccountID(rand.Intn(bank.numAccounts))
		to := makeAccountID(rand.Intn(bank.numAccounts))
		// Continue when from == to
		if bytes.Equal(from, to) {
			continue
		}
		exchangeAmount := rand.Int63n(100)
		// transferMoney transfers exchangeAmount between the two accounts
		transferMoney := func(runner client.Runner) error {
			batchRead := &client.Batch{}
			batchRead.Get(from, to)
			if err := runner.Run(batchRead); err != nil {
				return err
			}
			if batchRead.Results[0].Err != nil {
				return batchRead.Results[0].Err
			}
			// Read from value.
			fromAccount := &Account{}
			err := fromAccount.decode(batchRead.Results[0].Rows[0].ValueBytes())
			if err != nil {
				return err
			}
			// Ensure there is enough cash.
			if fromAccount.Balance < exchangeAmount {
				return nil
			}
			// Read to value.
			toAccount := &Account{}
			errRead := toAccount.decode(batchRead.Results[0].Rows[1].ValueBytes())
			if errRead != nil {
				return errRead
			}
			// Update both accounts.
			batchWrite := &client.Batch{}
			fromAccount.Balance -= exchangeAmount
			toAccount.Balance += exchangeAmount
			if fromValue, err := fromAccount.encode(); err != nil {
				return err
			} else if toValue, err := toAccount.encode(); err != nil {
				return err
			} else {
				batchWrite.Put(fromValue, toValue)
			}
			return runner.Run(batchWrite)
		}
		if *useTransaction {
			if err := bank.db.Tx(func(tx *client.Tx) error { return transferMoney(tx) }); err != nil {
				log.Fatal(err)
			}
		} else if err := transferMoney(bank.db); err != nil {
			log.Fatal(err)
		}
		atomic.AddInt32(&bank.numTransfers, 1)
	}
}

// Initialize all the bank accounts with cash.
func (bank *Bank) initBankAccounts(cash int64) {
	batch := &client.Batch{}
	account := Account{Balance: cash}
	value, err := account.encode()
	if err != nil {
		log.Fatal(err)
	}
	for i := 0; i < bank.numAccounts; i++ {
		batch = batch.Put(makeAccountID(i), value)
	}
	if err := bank.db.Run(batch); err != nil {
		log.Fatal(err)
	}
	log.Info("Done initializing all accounts\n")
}

func (bank *Bank) periodicallyCheckBalances(initCash int64) {
	for {
		// Sleep for a bit to allow money transfers to happen in the background.
		time.Sleep(time.Second)
		fmt.Printf("%d transfers were executed.\n\n", bank.numTransfers)
		// Check that all the money is accounted for.
		totalAmount := bank.sumAllAccounts()
		if totalAmount != int64(bank.numAccounts)*initCash {
			err := fmt.Sprintf("\nTotal cash in the bank = %d.\n", totalAmount)
			log.Fatal(err)
		}
		fmt.Printf("\nThe bank is in good order\n\n")
	}
}

func main() {
	fmt.Printf("A simple program that keeps moving money between bank accounts.\n\n")
	flag.Parse()
	if !*useTransaction {
		fmt.Printf("Use of a transaction has been disabled.\n")
	}
	// Run a test cockroach instance to represent the bank.
	security.SetReadFileFn(securitytest.Asset)
	serv := server.StartTestServer(nil)
	defer serv.Stop()
	// Initialize the bank.
	var bank Bank
	bank.numAccounts = 10
	// Create a database handle
	db, err := client.Open("https://root@" + serv.ServingAddr() + "?certs=test_certs")
	if err != nil {
		log.Fatal(err)
	}
	bank.db = db
	// Initialize all the bank accounts.
	const initCash = 1000
	bank.initBankAccounts(initCash)

	// Start all the money transfer routines.
	const numTransferRoutines = 1000
	for i := 0; i < numTransferRoutines; i++ {
		go bank.continuousMoneyTransfer()
	}

	bank.periodicallyCheckBalances(initCash)
}
<commit_msg>Further update using new API.<commit_after>// Copyright 2015 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License. See the AUTHORS file
// for names of contributors.
//
// Author: Vivek Menezes (vivek.menezes@gmail.com)

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"math/rand"
	"sync/atomic"
	"time"

	"github.com/cockroachdb/cockroach/client"
	"github.com/cockroachdb/cockroach/security"
	"github.com/cockroachdb/cockroach/security/securitytest"
	"github.com/cockroachdb/cockroach/server"
	"github.com/cockroachdb/cockroach/util/log"
)

var useTransaction = flag.Bool("use-transaction", true, "Turn off to disable transaction.")

// Makes an id string from an id int.
func makeAccountID(id int) []byte {
	return []byte(fmt.Sprintf("%09d", id))
}

// Bank stores all the bank related state.
type Bank struct {
	db           *client.DB
	numAccounts  int
	numTransfers int32
}

// Account holds all the customers account information
type Account struct {
	Balance int64
}

func (a Account) encode() ([]byte, error) {
	return json.Marshal(a)
}

func (a *Account) decode(b []byte) error {
	return json.Unmarshal(b, a)
}

// Read the balances in all the accounts and return them.
func (bank *Bank) sumAllAccounts() int64 {
	var result int64
	err := bank.db.Tx(func(tx *client.Tx) error {
		scan, err := tx.Scan(makeAccountID(0), makeAccountID(bank.numAccounts), int64(bank.numAccounts))
		if err != nil {
			return err
		}
		if len(scan.Rows) != bank.numAccounts {
			return fmt.Errorf("Could only read %d of %d rows of the database.\n", len(scan.Rows), bank.numAccounts)
		}
		// Copy responses into balances.
		for i := 0; i < bank.numAccounts; i++ {
			account := &Account{}
			err := account.decode(scan.Rows[i].ValueBytes())
			if err != nil {
				return err
			}
			// fmt.Printf("Account %d contains %d$\n", i, account.Balance)
			result += account.Balance
		}
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}
	return result
}

// continuouslyTransferMoney() keeps moving random amounts between
// random accounts.
func (bank *Bank) continuousMoneyTransfer() {
	for {
		from := makeAccountID(rand.Intn(bank.numAccounts))
		to := makeAccountID(rand.Intn(bank.numAccounts))
		// Continue when from == to
		if bytes.Equal(from, to) {
			continue
		}
		exchangeAmount := rand.Int63n(100)
		// transferMoney transfers exchangeAmount between the two accounts
		transferMoney := func(runner client.Runner) error {
			batchRead := &client.Batch{}
			batchRead.Get(from, to)
			if err := runner.Run(batchRead); err != nil {
				return err
			}
			if batchRead.Results[0].Err != nil {
				return batchRead.Results[0].Err
			}
			// Read from value.
			fromAccount := &Account{}
			err := fromAccount.decode(batchRead.Results[0].Rows[0].ValueBytes())
			if err != nil {
				return err
			}
			// Ensure there is enough cash.
			if fromAccount.Balance < exchangeAmount {
				return nil
			}
			// Read to value.
			toAccount := &Account{}
			errRead := toAccount.decode(batchRead.Results[0].Rows[1].ValueBytes())
			if errRead != nil {
				return errRead
			}
			// Update both accounts.
			batchWrite := &client.Batch{}
			fromAccount.Balance -= exchangeAmount
			toAccount.Balance += exchangeAmount
			if fromValue, err := fromAccount.encode(); err != nil {
				return err
			} else if toValue, err := toAccount.encode(); err != nil {
				return err
			} else {
				batchWrite.Put(fromValue, toValue)
			}
			return runner.Run(batchWrite)
		}
		if *useTransaction {
			if err := bank.db.Tx(func(tx *client.Tx) error { return transferMoney(tx) }); err != nil {
				log.Fatal(err)
			}
		} else if err := transferMoney(bank.db); err != nil {
			log.Fatal(err)
		}
		atomic.AddInt32(&bank.numTransfers, 1)
	}
}

// Initialize all the bank accounts with cash.
func (bank *Bank) initBankAccounts(cash int64) {
	batch := &client.Batch{}
	account := Account{Balance: cash}
	value, err := account.encode()
	if err != nil {
		log.Fatal(err)
	}
	for i := 0; i < bank.numAccounts; i++ {
		batch.Put(makeAccountID(i), value)
	}
	if err := bank.db.Run(batch); err != nil {
		log.Fatal(err)
	}
	log.Info("Done initializing all accounts\n")
}

func (bank *Bank) periodicallyCheckBalances(initCash int64) {
	for {
		// Sleep for a bit to allow money transfers to happen in the background.
		time.Sleep(time.Second)
		fmt.Printf("%d transfers were executed.\n\n", bank.numTransfers)
		// Check that all the money is accounted for.
		totalAmount := bank.sumAllAccounts()
		if totalAmount != int64(bank.numAccounts)*initCash {
			err := fmt.Sprintf("\nTotal cash in the bank = %d.\n", totalAmount)
			log.Fatal(err)
		}
		fmt.Printf("\nThe bank is in good order\n\n")
	}
}

func main() {
	fmt.Printf("A simple program that keeps moving money between bank accounts.\n\n")
	flag.Parse()
	if !*useTransaction {
		fmt.Printf("Use of a transaction has been disabled.\n")
	}
	// Run a test cockroach instance to represent the bank.
	security.SetReadFileFn(securitytest.Asset)
	serv := server.StartTestServer(nil)
	defer serv.Stop()
	// Initialize the bank.
	var bank Bank
	bank.numAccounts = 10
	// Create a database handle
	db, err := client.Open("https://root@" + serv.ServingAddr() + "?certs=test_certs")
	if err != nil {
		log.Fatal(err)
	}
	bank.db = db
	// Initialize all the bank accounts.
	const initCash = 1000
	bank.initBankAccounts(initCash)

	// Start all the money transfer routines.
	const numTransferRoutines = 1000
	for i := 0; i < numTransferRoutines; i++ {
		go bank.continuousMoneyTransfer()
	}

	bank.periodicallyCheckBalances(initCash)
}
<|endoftext|>


<commit_before>package hstspreload

import (
	"fmt"
	"strconv"
	"strings"
)

const (
	// MaxAgeNotPresent indicates that a HSTSHeader.MaxAge value is invalid.
	MaxAgeNotPresent = (-1)

	// 18 weeks
	hstsMinimumMaxAge = 10886400 // seconds

	tenYears = 86400 * 365 * 10 // seconds
)

// An HSTSHeader stores the semantics of an HSTS header.
//
// Note: Unless all values are known at initialization time, use
// NewHSTSHeader() instead of constructing an HSTSHeader directly.
// This ensures that the MaxAge field is initialized to
// MaxAgeNotPresent.
type HSTSHeader struct {
	// MaxAge == MaxAgeNotPresent indicates that this value is invalid.
	// A valid MaxAge value is a non-negative integer.
	MaxAge            int64
	IncludeSubDomains bool
	Preload           bool
}

// NewHSTSHeader constructs a new header with all directive values un-set.
//
// It is requivalent to:
//
//     HSTSHeader{
//       Preload:           false,
//       IncludeSubDomains: false,
//       MaxAge:            MaxAgeNotPresent,
//     }
func NewHSTSHeader() HSTSHeader {
	return HSTSHeader{
		Preload:           false,
		IncludeSubDomains: false,
		MaxAge:            MaxAgeNotPresent,
	}
}

// Iff Issues has no errors, the output integer is the max-age in seconds.
func parseMaxAge(directive string) (int64, Issues) {
	issues := Issues{}
	maxAgeNumericalString := directive[8:]

	// TODO: Use more concise validation code to parse a digit string to a signed int.
	for i, c := range maxAgeNumericalString {
		if i == 0 && c == '0' && len(maxAgeNumericalString) > 1 {
			issues = issues.addWarningf(
				"header.parse.max_age.leading_zero",
				"Unexpected max-age syntax",
				"The header's max-age value contains a leading 0: `%s`", directive)
		}
		if c < '0' || c > '9' {
			return MaxAgeNotPresent, issues.addErrorf(
				"header.parse.max_age.non_digit_characters",
				"Invalid max-age syntax",
				"The header's max-age value contains characters that are not digits: `%s`", directive)
		}
	}

	maxAge, err := strconv.ParseInt(maxAgeNumericalString, 10, 64)

	if err != nil {
		return MaxAgeNotPresent, issues.addErrorf(
			"header.parse.max_age.parse_int_error",
			"Invalid max-age syntax",
			"We could not parse the header's max-age value `%s`.", maxAgeNumericalString)
	}

	if maxAge < 0 {
		return MaxAgeNotPresent, issues.addErrorf(
			"internal.header.parse.max_age.negative",
			"Invalid max-age syntax",
			"Parsing the header's max-age resulted in an unexpected negative integer: `%d`", maxAge)
	}

	return maxAge, issues
}

// ParseHeaderString parses an HSTS header. ParseHeaderString will
// report syntax errors and warnings, but does NOT calculate whether the
// header value is semantically valid. (See PreloadableHeaderString() for
// that.)
//
// To interpret the Issues that are returned, see the list of
// conventions in the documentation for Issues.
func ParseHeaderString(headerString string) (HSTSHeader, Issues) {
	hstsHeader := NewHSTSHeader()
	issues := Issues{}

	directives := strings.Split(headerString, ";")
	for i, directive := range directives {
		// TODO: this trims more than spaces and tabs (LWS). https://crbug.com/596561#c10
		directives[i] = strings.TrimSpace(directive)
	}

	// If strings.Split() is given whitespace, it still returns an (empty) directive.
	// So we handle this case separately.
	if len(directives) == 1 && directives[0] == "" {
		// Return immediately, because all the extra information is redundant.
		return hstsHeader, issues.addWarningf(
			"header.parse.empty",
			"Empty Header",
			"The HSTS header is empty.")
	}

	for _, directive := range directives {
		directiveEqualsIgnoringCase := func(s string) bool {
			return strings.EqualFold(directive, s)
		}

		directiveHasPrefixIgnoringCase := func(prefix string) bool {
			return strings.HasPrefix(strings.ToLower(directive), strings.ToLower(prefix))
		}

		switch {
		case directiveEqualsIgnoringCase("preload"):
			if hstsHeader.Preload {
				issues = issues.addUniqueWarningf(
					"header.parse.repeated.preload",
					"Repeated preload directive",
					"Header contains a repeated directive: `preload`")
			} else {
				hstsHeader.Preload = true
			}

		case directiveHasPrefixIgnoringCase("preload"):
			issues = issues.addUniqueWarningf(
				"header.parse.invalid.preload",
				"Invalid preload directive",
				"Header contains a `preload` directive with extra parts.")

		case directiveEqualsIgnoringCase("includeSubDomains"):
			if hstsHeader.IncludeSubDomains {
				issues = issues.addUniqueWarningf(
					"header.parse.repeated.include_sub_domains",
					"Repeated includeSubDomains directive",
					"Header contains a repeated directive: `includeSubDomains`")
			} else {
				hstsHeader.IncludeSubDomains = true
				if directive != "includeSubDomains" {
					issues = issues.addUniqueWarningf(
						"header.parse.spelling.include_sub_domains",
						"Non-standard capitalization of includeSubDomains",
						"Header contains the token `%s`. The recommended capitalization is `includeSubDomains`.",
						directive,
					)
				}
			}

		case directiveHasPrefixIgnoringCase("includeSubDomains"):
			issues = issues.addUniqueWarningf(
				"header.parse.invalid.include_sub_domains",
				"Invalid includeSubDomains directive",
				"The header contains an `includeSubDomains` directive with extra directives.")

		case directiveHasPrefixIgnoringCase("max-age="):
			maxAge, maxAgeIssues := parseMaxAge(directive)
			issues = combineIssues(issues, maxAgeIssues)

			if len(maxAgeIssues.Errors) > 0 {
				continue
			}

			if hstsHeader.MaxAge == MaxAgeNotPresent {
				hstsHeader.MaxAge = maxAge
			} else {
				issues = issues.addUniqueWarningf(
					"header.parse.repeated.max_age",
					"Repeated max-age directive",
					"The header contains a repeated directive: `max-age`")
			}

		case directiveHasPrefixIgnoringCase("max-age"):
			issues = issues.addUniqueErrorf(
				"header.parse.invalid.max_age.no_value",
				"Max-age drective without a value",
				"The header contains a max-age directive name without an associated value. Please specify the max-age in seconds.")

		case directiveEqualsIgnoringCase(""):
			issues = issues.addUniqueWarningf(
				"header.parse.empty_directive",
				"Empty directive or extra semicolon",
				"The header includes an empty directive or extra semicolon.")

		default:
			issues = issues.addWarningf(
				"header.parse.unknown_directive",
				"Unknown directive",
				"The header contains an unknown directive: `%s`", directive)
		}
	}
	return hstsHeader, issues
}

func preloadableHeaderPreload(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	if !hstsHeader.Preload {
		issues = issues.addErrorf(
			"header.preloadable.preload.missing",
			"No preload directive",
			"The header must contain the `preload` directive.")
	}

	return issues
}

func preloadableHeaderSubDomains(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	if !hstsHeader.IncludeSubDomains {
		issues = issues.addErrorf(
			"header.preloadable.include_sub_domains.missing",
			"No includeSubDomains directive",
			"The header must contain the `includeSubDomains` directive.")
	}

	return issues
}

func preloadableHeaderMaxAge(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	switch {
	case hstsHeader.MaxAge == MaxAgeNotPresent:
		issues = issues.addErrorf(
			"header.preloadable.max_age.missing",
			"No max-age directice",
			"Header requirement error: Header must contain a valid `max-age` directive.")

	case hstsHeader.MaxAge < 0:
		issues = issues.addErrorf(
			"internal.header.preloadable.max_age.negative",
			"Negative max-age",
			"Encountered an HSTSHeader with a negative max-age that does not equal MaxAgeNotPresent: %d", hstsHeader.MaxAge)

	case hstsHeader.MaxAge < hstsMinimumMaxAge:
		errorStr := fmt.Sprintf(
			"The max-age must be at least 10886400 seconds (== 18 weeks), but the header currently only has max-age=%d.",
			hstsHeader.MaxAge,
		)
		if hstsHeader.MaxAge == 0 {
			errorStr += " If you are trying to remove this domain from the preload list, please contact Lucas Garron at hstspreload@chromium.org"
			issues = issues.addErrorf(
				"header.preloadable.max_age.zero",
				"Max-age is 0",
				errorStr,
			)
		} else {
			issues = issues.addErrorf(
				"header.preloadable.max_age.too_low",
				"Max-age too low",
				errorStr,
			)
		}

	case hstsHeader.MaxAge > tenYears:
		issues = issues.addWarningf(
			"header.preloadable.max_age.over_10_years",
			"Max-age > 10 years",
			"FYI: The max-age (%d seconds) is longer than 10 years, which is an unusually long value.",
			hstsHeader.MaxAge,
		)

	}

	return issues
}

// PreloadableHeader checks whether hstsHeader satisfies all requirements
// for preloading in Chromium.
//
// To interpret the result, see the list of conventions in the
// documentation for Issues.
//
// Most of the time, you'll probably want to use PreloadableHeaderString() instead.
func PreloadableHeader(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	issues = combineIssues(issues, preloadableHeaderSubDomains(hstsHeader))
	issues = combineIssues(issues, preloadableHeaderPreload(hstsHeader))
	issues = combineIssues(issues, preloadableHeaderMaxAge(hstsHeader))
	return issues
}

func RemovableHeader(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	if hstsHeader.Preload {
		issues = issues.addErrorf(
			"header.removable.contains.preload",
			"Contains preload directive",
			"Header requirement error: For preload list removal, the header must not contain the `preload` directive.")
	}

	if hstsHeader.MaxAge == MaxAgeNotPresent {
		issues = issues.addErrorf(
			"header.removable.missing.max_age",
			"No max-age directive",
			"Header requirement error: Header must contain a valid `max-age` directive.")
	}

	return issues
}

// PreloadableHeaderString is a convenience function that calls
// ParseHeaderString() and then calls on PreloadableHeader() the parsed
// header. It returns all issues from both calls, combined.
//
// To interpret the result, see the list of conventions in the
// documentation for Issues.
func PreloadableHeaderString(headerString string) Issues {
	hstsHeader, issues := ParseHeaderString(headerString)
	return combineIssues(issues, PreloadableHeader(hstsHeader))
}

// RemovableHeaderString is a convenience function that calls
// ParseHeaderString() and then calls on RemovableHeader() the parsed
// header. It returns all errors from ParseHeaderString() and all
// issues from RemovableHeader(). Note that *warnings* from
// ParseHeaderString() are ignored, since domains asking to be removed
// will often have minor errors that shouldn't affect removal. It's
// better to have a cleaner verdict in this case.
//
// To interpret the result, see the list of conventions in the
// documentation for Issues.
func RemovableHeaderString(headerString string) Issues {
	hstsHeader, issues := ParseHeaderString(headerString)
	issues = Issues{
		Errors: issues.Errors,
		// Ignore parse warnings for removal testing.
	}
	return combineIssues(issues, RemovableHeader(hstsHeader))
}
<commit_msg>Add documentation for RemovableHeader().<commit_after>package hstspreload

import (
	"fmt"
	"strconv"
	"strings"
)

const (
	// MaxAgeNotPresent indicates that a HSTSHeader.MaxAge value is invalid.
	MaxAgeNotPresent = (-1)

	// 18 weeks
	hstsMinimumMaxAge = 10886400 // seconds

	tenYears = 86400 * 365 * 10 // seconds
)

// An HSTSHeader stores the semantics of an HSTS header.
//
// Note: Unless all values are known at initialization time, use
// NewHSTSHeader() instead of constructing an HSTSHeader directly.
// This ensures that the MaxAge field is initialized to
// MaxAgeNotPresent.
type HSTSHeader struct {
	// MaxAge == MaxAgeNotPresent indicates that this value is invalid.
	// A valid MaxAge value is a non-negative integer.
	MaxAge            int64
	IncludeSubDomains bool
	Preload           bool
}

// NewHSTSHeader constructs a new header with all directive values un-set.
//
// It is requivalent to:
//
//     HSTSHeader{
//       Preload:           false,
//       IncludeSubDomains: false,
//       MaxAge:            MaxAgeNotPresent,
//     }
func NewHSTSHeader() HSTSHeader {
	return HSTSHeader{
		Preload:           false,
		IncludeSubDomains: false,
		MaxAge:            MaxAgeNotPresent,
	}
}

// Iff Issues has no errors, the output integer is the max-age in seconds.
func parseMaxAge(directive string) (int64, Issues) {
	issues := Issues{}
	maxAgeNumericalString := directive[8:]

	// TODO: Use more concise validation code to parse a digit string to a signed int.
	for i, c := range maxAgeNumericalString {
		if i == 0 && c == '0' && len(maxAgeNumericalString) > 1 {
			issues = issues.addWarningf(
				"header.parse.max_age.leading_zero",
				"Unexpected max-age syntax",
				"The header's max-age value contains a leading 0: `%s`", directive)
		}
		if c < '0' || c > '9' {
			return MaxAgeNotPresent, issues.addErrorf(
				"header.parse.max_age.non_digit_characters",
				"Invalid max-age syntax",
				"The header's max-age value contains characters that are not digits: `%s`", directive)
		}
	}

	maxAge, err := strconv.ParseInt(maxAgeNumericalString, 10, 64)

	if err != nil {
		return MaxAgeNotPresent, issues.addErrorf(
			"header.parse.max_age.parse_int_error",
			"Invalid max-age syntax",
			"We could not parse the header's max-age value `%s`.", maxAgeNumericalString)
	}

	if maxAge < 0 {
		return MaxAgeNotPresent, issues.addErrorf(
			"internal.header.parse.max_age.negative",
			"Invalid max-age syntax",
			"Parsing the header's max-age resulted in an unexpected negative integer: `%d`", maxAge)
	}

	return maxAge, issues
}

// ParseHeaderString parses an HSTS header. ParseHeaderString will
// report syntax errors and warnings, but does NOT calculate whether the
// header value is semantically valid. (See PreloadableHeaderString() for
// that.)
//
// To interpret the Issues that are returned, see the list of
// conventions in the documentation for Issues.
func ParseHeaderString(headerString string) (HSTSHeader, Issues) {
	hstsHeader := NewHSTSHeader()
	issues := Issues{}

	directives := strings.Split(headerString, ";")
	for i, directive := range directives {
		// TODO: this trims more than spaces and tabs (LWS). https://crbug.com/596561#c10
		directives[i] = strings.TrimSpace(directive)
	}

	// If strings.Split() is given whitespace, it still returns an (empty) directive.
	// So we handle this case separately.
	if len(directives) == 1 && directives[0] == "" {
		// Return immediately, because all the extra information is redundant.
		return hstsHeader, issues.addWarningf(
			"header.parse.empty",
			"Empty Header",
			"The HSTS header is empty.")
	}

	for _, directive := range directives {
		directiveEqualsIgnoringCase := func(s string) bool {
			return strings.EqualFold(directive, s)
		}

		directiveHasPrefixIgnoringCase := func(prefix string) bool {
			return strings.HasPrefix(strings.ToLower(directive), strings.ToLower(prefix))
		}

		switch {
		case directiveEqualsIgnoringCase("preload"):
			if hstsHeader.Preload {
				issues = issues.addUniqueWarningf(
					"header.parse.repeated.preload",
					"Repeated preload directive",
					"Header contains a repeated directive: `preload`")
			} else {
				hstsHeader.Preload = true
			}

		case directiveHasPrefixIgnoringCase("preload"):
			issues = issues.addUniqueWarningf(
				"header.parse.invalid.preload",
				"Invalid preload directive",
				"Header contains a `preload` directive with extra parts.")

		case directiveEqualsIgnoringCase("includeSubDomains"):
			if hstsHeader.IncludeSubDomains {
				issues = issues.addUniqueWarningf(
					"header.parse.repeated.include_sub_domains",
					"Repeated includeSubDomains directive",
					"Header contains a repeated directive: `includeSubDomains`")
			} else {
				hstsHeader.IncludeSubDomains = true
				if directive != "includeSubDomains" {
					issues = issues.addUniqueWarningf(
						"header.parse.spelling.include_sub_domains",
						"Non-standard capitalization of includeSubDomains",
						"Header contains the token `%s`. The recommended capitalization is `includeSubDomains`.",
						directive,
					)
				}
			}

		case directiveHasPrefixIgnoringCase("includeSubDomains"):
			issues = issues.addUniqueWarningf(
				"header.parse.invalid.include_sub_domains",
				"Invalid includeSubDomains directive",
				"The header contains an `includeSubDomains` directive with extra directives.")

		case directiveHasPrefixIgnoringCase("max-age="):
			maxAge, maxAgeIssues := parseMaxAge(directive)
			issues = combineIssues(issues, maxAgeIssues)

			if len(maxAgeIssues.Errors) > 0 {
				continue
			}

			if hstsHeader.MaxAge == MaxAgeNotPresent {
				hstsHeader.MaxAge = maxAge
			} else {
				issues = issues.addUniqueWarningf(
					"header.parse.repeated.max_age",
					"Repeated max-age directive",
					"The header contains a repeated directive: `max-age`")
			}

		case directiveHasPrefixIgnoringCase("max-age"):
			issues = issues.addUniqueErrorf(
				"header.parse.invalid.max_age.no_value",
				"Max-age drective without a value",
				"The header contains a max-age directive name without an associated value. Please specify the max-age in seconds.")

		case directiveEqualsIgnoringCase(""):
			issues = issues.addUniqueWarningf(
				"header.parse.empty_directive",
				"Empty directive or extra semicolon",
				"The header includes an empty directive or extra semicolon.")

		default:
			issues = issues.addWarningf(
				"header.parse.unknown_directive",
				"Unknown directive",
				"The header contains an unknown directive: `%s`", directive)
		}
	}
	return hstsHeader, issues
}

func preloadableHeaderPreload(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	if !hstsHeader.Preload {
		issues = issues.addErrorf(
			"header.preloadable.preload.missing",
			"No preload directive",
			"The header must contain the `preload` directive.")
	}

	return issues
}

func preloadableHeaderSubDomains(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	if !hstsHeader.IncludeSubDomains {
		issues = issues.addErrorf(
			"header.preloadable.include_sub_domains.missing",
			"No includeSubDomains directive",
			"The header must contain the `includeSubDomains` directive.")
	}

	return issues
}

func preloadableHeaderMaxAge(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	switch {
	case hstsHeader.MaxAge == MaxAgeNotPresent:
		issues = issues.addErrorf(
			"header.preloadable.max_age.missing",
			"No max-age directice",
			"Header requirement error: Header must contain a valid `max-age` directive.")

	case hstsHeader.MaxAge < 0:
		issues = issues.addErrorf(
			"internal.header.preloadable.max_age.negative",
			"Negative max-age",
			"Encountered an HSTSHeader with a negative max-age that does not equal MaxAgeNotPresent: %d", hstsHeader.MaxAge)

	case hstsHeader.MaxAge < hstsMinimumMaxAge:
		errorStr := fmt.Sprintf(
			"The max-age must be at least 10886400 seconds (== 18 weeks), but the header currently only has max-age=%d.",
			hstsHeader.MaxAge,
		)
		if hstsHeader.MaxAge == 0 {
			errorStr += " If you are trying to remove this domain from the preload list, please contact Lucas Garron at hstspreload@chromium.org"
			issues = issues.addErrorf(
				"header.preloadable.max_age.zero",
				"Max-age is 0",
				errorStr,
			)
		} else {
			issues = issues.addErrorf(
				"header.preloadable.max_age.too_low",
				"Max-age too low",
				errorStr,
			)
		}

	case hstsHeader.MaxAge > tenYears:
		issues = issues.addWarningf(
			"header.preloadable.max_age.over_10_years",
			"Max-age > 10 years",
			"FYI: The max-age (%d seconds) is longer than 10 years, which is an unusually long value.",
			hstsHeader.MaxAge,
		)

	}

	return issues
}

// PreloadableHeader checks whether hstsHeader satisfies all requirements
// for preloading in Chromium.
//
// To interpret the result, see the list of conventions in the
// documentation for Issues.
//
// Most of the time, you'll probably want to use PreloadableHeaderString() instead.
func PreloadableHeader(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	issues = combineIssues(issues, preloadableHeaderSubDomains(hstsHeader))
	issues = combineIssues(issues, preloadableHeaderPreload(hstsHeader))
	issues = combineIssues(issues, preloadableHeaderMaxAge(hstsHeader))
	return issues
}

// RemovableHeader checks whether the header satisfies all requirements
// for being removed from the Chromium preload list.
//
// To interpret the result, see the list of conventions in the
// documentation for Issues.
//
// Most of the time, you'll probably want to use RemovableHeaderString() instead.
func RemovableHeader(hstsHeader HSTSHeader) Issues {
	issues := Issues{}

	if hstsHeader.Preload {
		issues = issues.addErrorf(
			"header.removable.contains.preload",
			"Contains preload directive",
			"Header requirement error: For preload list removal, the header must not contain the `preload` directive.")
	}

	if hstsHeader.MaxAge == MaxAgeNotPresent {
		issues = issues.addErrorf(
			"header.removable.missing.max_age",
			"No max-age directive",
			"Header requirement error: Header must contain a valid `max-age` directive.")
	}

	return issues
}

// PreloadableHeaderString is a convenience function that calls
// ParseHeaderString() and then calls on PreloadableHeader() the parsed
// header. It returns all issues from both calls, combined.
//
// To interpret the result, see the list of conventions in the
// documentation for Issues.
func PreloadableHeaderString(headerString string) Issues {
	hstsHeader, issues := ParseHeaderString(headerString)
	return combineIssues(issues, PreloadableHeader(hstsHeader))
}

// RemovableHeaderString is a convenience function that calls
// ParseHeaderString() and then calls on RemovableHeader() the parsed
// header. It returns all errors from ParseHeaderString() and all
// issues from RemovableHeader(). Note that *warnings* from
// ParseHeaderString() are ignored, since domains asking to be removed
// will often have minor errors that shouldn't affect removal. It's
// better to have a cleaner verdict in this case.
//
// To interpret the result, see the list of conventions in the
// documentation for Issues.
func RemovableHeaderString(headerString string) Issues {
	hstsHeader, issues := ParseHeaderString(headerString)
	issues = Issues{
		Errors: issues.Errors,
		// Ignore parse warnings for removal testing.
	}
	return combineIssues(issues, RemovableHeader(hstsHeader))
}
<|endoftext|>