<commit_before>package bot

import (
	"fmt"
	"strings"
	"time"

	"strconv"

	"github.com/nlopes/slack"
)

var (
	prefix        string = "party_"
	sprefix       string = "s_" + prefix
	partyIndexKey string = sprefix + "keys"
	meuName       string = "파티 모집원 메우"
)

func rescheduleParty(bot *Meu) {
	items, err := bot.rc.Keys(prefix + "*")
	if err != nil {
		return
	}
	now := time.Now()
	for _, key := range items {
		saved_time, keyword := parseKey(key)
		if now.After(saved_time) {
			members, _ := bot.rc.SetList(key)
			for _, member := range members {
				bot.rc.SetRemove(sprefix+member, key)
			}
			bot.rc.Erase(key)
		} else {
			scheduleParty(bot, &saved_time, keyword)
			registerToIndex(bot, &saved_time, key)
		}
	}

	bot.rc.SortedSetRemoveRange(partyIndexKey, 0, now.Unix())
}

func parseKey(key string) (time.Time, string) {
	parts := strings.Split(key, "_")
	sec, _ := strconv.Atoi(parts[1])
	return time.Unix(int64(sec), 0), parts[2]
}

func registerToIndex(bot *Meu, date *time.Time, key string) {
	if _, err := bot.rc.SortedSetRank(partyIndexKey, key).Result(); err != nil {
		bot.rc.SortedSetAdd(partyIndexKey, int(date.Unix()), key)
	}
}

func scheduleParty(bot *Meu, date *time.Time, keyword string) {
	bot.cr.AddFunc(fmt.Sprintf("0 %d %d %d %d *", date.Minute(), date.Hour(), date.Day(), date.Month()), alarmFuncGenerator(bot, keyword, partyKey(date, keyword)))
}

func partyKey(date *time.Time, keyword string) string {
	return prefix + strconv.FormatInt(date.Unix(), 10) + "_" + keyword
}

func alarmFuncGenerator(bot *Meu, keyword string, key string) func() {
	return func() {
		list, err := bot.rc.SetList(key)
		bot.rc.Erase(key)
		if err == nil {
			members := make([]string, len(list))
			for i, item := range list {
				members[i] = fmt.Sprintf("<@%s>", item)
				bot.rc.SetRemove(sprefix+item, key)
			}
			bot.PostMessage("#random", fmt.Sprintf("'%s' 파티 10분 전이다 메우. %s", keyword, strings.Join(members, " ")), slack.PostMessageParameters{
				AsUser:    false,
				Username:  meuName,
				IconEmoji: ":meu:",
			})
		}
	}
}

func correctDate(matched []string) *time.Time {
	now := time.Now()
	month, err := strconv.Atoi(matched[0])
	var not_set struct {
		month bool
		day   bool
	}
	if err != nil {
		month = int(now.Month())
		not_set.month = true
	}
	day, err := strconv.Atoi(matched[1])
	if err != nil {
		day = now.Day()
		not_set.day = true
	}
	hour, err := strconv.Atoi(matched[2])
	if err != nil {
		return nil
	}
	min, err := strconv.Atoi(matched[3])
	if err != nil {
		min = 0
	}

	date := time.Date(now.Year(), time.Month(month), day, hour, min, 0, 0, now.Location())
	if date.Before(now) {
		corrected := false
		// first try after 12 hour
		if not_set.day {
			if date.Hour() < 12 {
				date = date.Add(time.Hour * 12)
				if corrected = !date.Before(now); !corrected {
					// reset
					date = date.Add(time.Hour * -12)
				}
			}
			if !corrected {
				date = date.AddDate(0, 0, 1)
			}
		} else if not_set.month {
			date = date.AddDate(0, 1, 0)
		} else {
			date = date.AddDate(1, 0, 0)
		}
		if date.Before(now) {
			return nil
		}
	}

	return &date
}

func event_key_to_slack_attach(key string) slack.Attachment {
	date, keyword := parseKey(key)
	return event_to_slack_attach(key, keyword, &date)
}

func event_to_slack_attach(key string, keyword string, date *time.Time) slack.Attachment {
	return slack.Attachment{
		Fields: []slack.AttachmentField{
			slack.AttachmentField{
				Title: "일시",
				Value: date.String(),
			},
			slack.AttachmentField{
				Title: "이름",
				Value: keyword,
			},
			slack.AttachmentField{
				Title: "파티 ID",
				Value: key,
			},
		},
	}
}

func register_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	keyword := strings.TrimSpace(matched[5])

	date := correctDate(matched[1:])
	if date == nil {
		bot.replySimple(e, "과거에 대해서 파티를 모집할 수 없다 메우")
		return
	}

	key := partyKey(date, keyword)
	inserted := bot.rc.SetAdd(key, e.User)
	registerToIndex(bot, date, key)
	responseData := slack.PostMessageParameters{
		AsUser:    false,
		IconEmoji: ":meu:",
		Username:  meuName,
		Attachments: []slack.Attachment{
			event_to_slack_attach(key, keyword, date),
		},
	}
	if inserted.Val() == 1 {
		bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 파티 대기에 들어갔다 메우", e.User), responseData)
		cardinal := bot.rc.SetCard(key)
		bot.rc.SetAdd(sprefix+e.User, key)
		if cardinal.Val() == 1 {
			scheduleParty(bot, date, keyword)
		}
	} else {
		bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 이미 들어가있는 파티다 메우.", e.User), responseData)
	}
}

func list_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	b_t := correctDate(matched[1:5])
	e_t := correctDate(matched[5:9])
	var (
		end   time.Time
		begin time.Time
	)
	if b_t == nil {
		keys, err := bot.rc.SetList(sprefix + e.User)
		if err != nil || len(keys) == 0 {
			bot.replySimple(e, "대기중인 파티가 없다 메우.")
		} else {
			attachments := make([]slack.Attachment, len(keys))
			for i, key := range keys {
				attachments[i] = event_key_to_slack_attach(key)
			}
			bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 지금 대기중인 파티는 다음과 같다 메우.", e.User), slack.PostMessageParameters{
				AsUser:      false,
				IconEmoji:   ":meu:",
				Username:    meuName,
				Attachments: attachments,
			})
		}
		return
	} else if e_t == nil {
		d, _ := time.ParseDuration("1h")
		end = begin.Add(d)
		d, _ = time.ParseDuration("-1h")
		begin = begin.Add(d)
	} else {
		end = *e_t
	}
	begin = *b_t

	keys, _ := bot.rc.SortedSetRange(partyIndexKey, begin.Unix(), end.Unix())
	attachments := make([]slack.AttachmentField, len(keys))
	for i, key := range keys {
		t, k := parseKey(key)
		attachments[i] = slack.AttachmentField{
			Title: k,
			Value: t.String(),
		}
	}

	bot.PostMessage(e.Channel,
		fmt.Sprintf("%s ~ %s 사이에 있는 파티 목록은 다음과 같다 메우.", begin.String(), end.String()),
		slack.PostMessageParameters{
			AsUser:    false,
			IconEmoji: ":meu:",
			Username:  meuName,
			Attachments: []slack.Attachment{
				slack.Attachment{
					Fields: attachments,
				},
			},
		})
}

func exit_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	key := strings.TrimSpace(matched[1])
	if bot.rc.SetRemove(key, e.User).Val() == 1 {
		bot.replySimple(e, "성공적으로 파티 대기에서 빠졌다 메우")
		bot.rc.SetRemove(sprefix+e.User, key)
		if bot.rc.SetCard(key).Val() == 0 {
			bot.rc.Erase(key)
			bot.rc.SortedSetRemove(partyIndexKey, key)
		}
	} else {
		bot.replySimple(e, "잘못된 파티 이름이거나 대기중이 아닌 파티이다 메우")
	}
}
<commit_msg>Fix party list error - single time given<commit_after>package bot

import (
	"fmt"
	"strings"
	"time"

	"strconv"

	"github.com/nlopes/slack"
)

var (
	prefix        string = "party_"
	sprefix       string = "s_" + prefix
	partyIndexKey string = sprefix + "keys"
	meuName       string = "파티 모집원 메우"
)

func rescheduleParty(bot *Meu) {
	items, err := bot.rc.Keys(prefix + "*")
	if err != nil {
		return
	}
	now := time.Now()
	for _, key := range items {
		saved_time, keyword := parseKey(key)
		if now.After(saved_time) {
			members, _ := bot.rc.SetList(key)
			for _, member := range members {
				bot.rc.SetRemove(sprefix+member, key)
			}
			bot.rc.Erase(key)
		} else {
			scheduleParty(bot, &saved_time, keyword)
			registerToIndex(bot, &saved_time, key)
		}
	}

	bot.rc.SortedSetRemoveRange(partyIndexKey, 0, now.Unix())
}

func parseKey(key string) (time.Time, string) {
	parts := strings.Split(key, "_")
	sec, _ := strconv.Atoi(parts[1])
	return time.Unix(int64(sec), 0), parts[2]
}

func registerToIndex(bot *Meu, date *time.Time, key string) {
	if _, err := bot.rc.SortedSetRank(partyIndexKey, key).Result(); err != nil {
		bot.rc.SortedSetAdd(partyIndexKey, int(date.Unix()), key)
	}
}

func scheduleParty(bot *Meu, date *time.Time, keyword string) {
	bot.cr.AddFunc(fmt.Sprintf("0 %d %d %d %d *", date.Minute(), date.Hour(), date.Day(), date.Month()), alarmFuncGenerator(bot, keyword, partyKey(date, keyword)))
}

func partyKey(date *time.Time, keyword string) string {
	return prefix + strconv.FormatInt(date.Unix(), 10) + "_" + keyword
}

func alarmFuncGenerator(bot *Meu, keyword string, key string) func() {
	return func() {
		list, err := bot.rc.SetList(key)
		bot.rc.Erase(key)
		if err == nil {
			members := make([]string, len(list))
			for i, item := range list {
				members[i] = fmt.Sprintf("<@%s>", item)
				bot.rc.SetRemove(sprefix+item, key)
			}
			bot.PostMessage("#random", fmt.Sprintf("'%s' 파티 10분 전이다 메우. %s", keyword, strings.Join(members, " ")), slack.PostMessageParameters{
				AsUser:    false,
				Username:  meuName,
				IconEmoji: ":meu:",
			})
		}
	}
}

func correctDate(matched []string) *time.Time {
	now := time.Now()
	month, err := strconv.Atoi(matched[0])
	var not_set struct {
		month bool
		day   bool
	}
	if err != nil {
		month = int(now.Month())
		not_set.month = true
	}
	day, err := strconv.Atoi(matched[1])
	if err != nil {
		day = now.Day()
		not_set.day = true
	}
	hour, err := strconv.Atoi(matched[2])
	if err != nil {
		return nil
	}
	min, err := strconv.Atoi(matched[3])
	if err != nil {
		min = 0
	}

	date := time.Date(now.Year(), time.Month(month), day, hour, min, 0, 0, now.Location())
	if date.Before(now) {
		corrected := false
		// first try after 12 hour
		if not_set.day {
			if date.Hour() < 12 {
				date = date.Add(time.Hour * 12)
				if corrected = !date.Before(now); !corrected {
					// reset
					date = date.Add(time.Hour * -12)
				}
			}
			if !corrected {
				date = date.AddDate(0, 0, 1)
			}
		} else if not_set.month {
			date = date.AddDate(0, 1, 0)
		} else {
			date = date.AddDate(1, 0, 0)
		}
		if date.Before(now) {
			return nil
		}
	}

	return &date
}

func event_key_to_slack_attach(key string) slack.Attachment {
	date, keyword := parseKey(key)
	return event_to_slack_attach(key, keyword, &date)
}

func event_to_slack_attach(key string, keyword string, date *time.Time) slack.Attachment {
	return slack.Attachment{
		Fields: []slack.AttachmentField{
			slack.AttachmentField{
				Title: "일시",
				Value: date.String(),
			},
			slack.AttachmentField{
				Title: "이름",
				Value: keyword,
			},
			slack.AttachmentField{
				Title: "파티 ID",
				Value: key,
			},
		},
	}
}

func register_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	keyword := strings.TrimSpace(matched[5])

	date := correctDate(matched[1:])
	if date == nil {
		bot.replySimple(e, "과거에 대해서 파티를 모집할 수 없다 메우")
		return
	}

	key := partyKey(date, keyword)
	inserted := bot.rc.SetAdd(key, e.User)
	registerToIndex(bot, date, key)
	responseData := slack.PostMessageParameters{
		AsUser:    false,
		IconEmoji: ":meu:",
		Username:  meuName,
		Attachments: []slack.Attachment{
			event_to_slack_attach(key, keyword, date),
		},
	}
	if inserted.Val() == 1 {
		bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 파티 대기에 들어갔다 메우", e.User), responseData)
		cardinal := bot.rc.SetCard(key)
		bot.rc.SetAdd(sprefix+e.User, key)
		if cardinal.Val() == 1 {
			scheduleParty(bot, date, keyword)
		}
	} else {
		bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 이미 들어가있는 파티다 메우.", e.User), responseData)
	}
}

func list_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	b_t := correctDate(matched[1:5])
	e_t := correctDate(matched[5:9])
	var (
		end   time.Time
		begin time.Time
	)
	if b_t == nil {
		keys, err := bot.rc.SetList(sprefix + e.User)
		if err != nil || len(keys) == 0 {
			bot.replySimple(e, "대기중인 파티가 없다 메우.")
		} else {
			attachments := make([]slack.Attachment, len(keys))
			for i, key := range keys {
				attachments[i] = event_key_to_slack_attach(key)
			}
			bot.PostMessage(e.Channel, fmt.Sprintf("<@%s> 지금 대기중인 파티는 다음과 같다 메우.", e.User), slack.PostMessageParameters{
				AsUser:      false,
				IconEmoji:   ":meu:",
				Username:    meuName,
				Attachments: attachments,
			})
		}
		return
	}
	begin = *b_t
	if e_t == nil {
		d, _ := time.ParseDuration("1h")
		end = begin.Add(d)
		d, _ = time.ParseDuration("-1h")
		begin = begin.Add(d)
	} else {
		end = *e_t
	}

	keys, _ := bot.rc.SortedSetRange(partyIndexKey, begin.Unix(), end.Unix())
	attachments := make([]slack.AttachmentField, len(keys))
	for i, key := range keys {
		t, k := parseKey(key)
		attachments[i] = slack.AttachmentField{
			Title: k,
			Value: t.String(),
		}
	}

	bot.PostMessage(e.Channel,
		fmt.Sprintf("%s ~ %s 사이에 있는 파티 목록은 다음과 같다 메우.", begin.String(), end.String()),
		slack.PostMessageParameters{
			AsUser:    false,
			IconEmoji: ":meu:",
			Username:  meuName,
			Attachments: []slack.Attachment{
				slack.Attachment{
					Fields: attachments,
				},
			},
		})
}

func exit_party(bot *Meu, e *slack.MessageEvent, matched []string) {
	key := strings.TrimSpace(matched[1])
	if bot.rc.SetRemove(key, e.User).Val() == 1 {
		bot.replySimple(e, "성공적으로 파티 대기에서 빠졌다 메우")
		bot.rc.SetRemove(sprefix+e.User, key)
		if bot.rc.SetCard(key).Val() == 0 {
			bot.rc.Erase(key)
			bot.rc.SortedSetRemove(partyIndexKey, key)
		}
	} else {
		bot.replySimple(e, "잘못된 파티 이름이거나 대기중이 아닌 파티이다 메우")
	}
}
<|endoftext|>


<commit_before>package sparta

// THIS FILE IS AUTOMATICALLY GENERATED
// DO NOT EDIT
// CREATED: 2021-01-17 06:59:25.122464 +0000 UTC

// SpartaGitHash is the commit hash of this Sparta library
const SpartaGitHash = "9114f55327d0bbcddbffce49b2a1c5e1f6ba341a"
<commit_msg>"Autogenerated build info"<commit_after>package sparta

// THIS FILE IS AUTOMATICALLY GENERATED
// DO NOT EDIT
// CREATED: 2021-01-17 20:34:12.522817 +0000 UTC

// SpartaGitHash is the commit hash of this Sparta library
const SpartaGitHash = "3553589a6d1bac128d4b57ef7d3d1eb7b7d4e12d"
<|endoftext|>


<commit_before>package cask

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

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

var testdataPath = "./testdata/"

// getWorkingDir returns a current working directory path. If it's not available
// prints an error to os.Stdout and exits with error status 1.
func getWorkingDir() string {
	pwd, err := os.Getwd()
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	return pwd
}

// getTestdata returns a file content as a byte array from provided testdata
// filename. If file not found, prints an error to os.Stdout and exits with exit
// status 1.
func getTestdata(filename string) []byte {
	path := filepath.Join(getWorkingDir(), testdataPath, filename)
	content, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Println(fmt.Errorf(err.Error()))
		os.Exit(1)
	}

	return content
}

func TestNewCask(t *testing.T) {
	// preparations
	c := NewCask("")

	// test
	assert.IsType(t, Cask{}, *c)
	assert.Empty(t, c.Token)
	assert.Empty(t, c.Content)
	assert.Len(t, c.Variants, 0)
}

func TestAddVariant(t *testing.T) {
	// preparations
	c := NewCask("")

	// test
	assert.Len(t, c.Variants, 0)
	c.AddVariant(*NewVariant())
	assert.Len(t, c.Variants, 1)
}

func TestCaskString(t *testing.T) {
	// preparations
	c := NewCask("")
	c.Token = "test"

	// test
	assert.Equal(t, "test", c.String())
}
<commit_msg>Add testdata tests<commit_after>package cask

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

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

var testdataPath = "./testdata/"

// getWorkingDir returns a current working directory path. If it's not available
// prints an error to os.Stdout and exits with error status 1.
func getWorkingDir() string {
	pwd, err := os.Getwd()
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	return pwd
}

// getTestdata returns a file content as a byte array from provided testdata
// filename. If file not found, prints an error to os.Stdout and exits with exit
// status 1.
func getTestdata(filename string) []byte {
	path := filepath.Join(getWorkingDir(), testdataPath, filename)
	content, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Println(fmt.Errorf(err.Error()))
		os.Exit(1)
	}

	return content
}

func TestNewCask(t *testing.T) {
	// preparations
	c := NewCask("")

	// test
	assert.IsType(t, Cask{}, *c)
	assert.Empty(t, c.Token)
	assert.Empty(t, c.Content)
	assert.Len(t, c.Variants, 0)
}

func TestParse(t *testing.T) {
	testCases := map[string]Cask{
		"if-six-versions-six-appcasts.rb": {
			Token:   "if-six-versions-six-appcasts",
			Content: string(getTestdata("if-six-versions-six-appcasts.rb")),
			Variants: []Variant{
				{
					Version: "0.1.0",
					SHA256:  "6ad9613a455798d6d92e5f5f390ab4baa70596bc869ed6b17f5cdd2b28635f06",
					URL:     "https://example.com/snowleopard/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version}/snowleopard.xml",
						Checkpoint: "a93e9e53c90ab95e1ce83cbc1cbd76102da1bce5330b649872dbd95a1793a03e",
						global:     false,
					},
					Names: []string{
						"Example",
						"Example (if-six-versions-six-appcasts)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-six-versions-six-appcasts).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
				{
					Version: "0.2.0",
					SHA256:  "911fc0c48cb0c70601db5775a9bef1b740dc4cc9f9b46389b9f0563fe7eb94d7",
					URL:     "https://example.com/lion/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version}/lion.xml",
						Checkpoint: "13dfb3758d65d265e4c12336815b2db327683ad38b2a1162cc88ab3579bbfaa1",
						global:     false,
					},
					Names: []string{
						"Example",
						"Example (if-six-versions-six-appcasts)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-six-versions-six-appcasts).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
				{
					Version: "0.3.0",
					SHA256:  "550613537fc488f3b372af74a4001879f012c8465b816f1b85c6d3446b2cfb49",
					URL:     "https://example.com/mountainlion/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version}/mountainlion.xml",
						Checkpoint: "00af55f25d0c6e53f017a972b77fe4def95f9bb4ec4dc217c520e875fa0071a9",
						global:     false,
					},
					Names: []string{
						"Example",
						"Example (if-six-versions-six-appcasts)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-six-versions-six-appcasts).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
				{
					Version: "0.4.0",
					SHA256:  "cd78534ed15ad46912b71339d1417d0d043d8309c2b94415f3ed1b9d1fdfaed0",
					URL:     "https://example.com/mavericks/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version}/mavericks.xml",
						Checkpoint: "9cbe5cfd22b0eb5f159ae634acf615d9c8032699b5a79d37a3046bdaf5677c84",
						global:     false,
					},
					Names: []string{
						"Example",
						"Example (if-six-versions-six-appcasts)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-six-versions-six-appcasts).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
				{
					Version: "0.5.0",
					SHA256:  "d1f62539db82b51da84bda2f4885db5e847db8389183be41389efd0ae6edab94",
					URL:     "https://example.com/yosemite/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version}/yosemite.xml",
						Checkpoint: "f309466aea57120e04b214292d54a9d5e32d018582344b3a62021a91ed8dd69d",
						global:     false,
					},
					Names: []string{
						"Example",
						"Example (if-six-versions-six-appcasts)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-six-versions-six-appcasts).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
				{
					Version: "2.0.0",
					SHA256:  "f22abd6773ab232869321ad4b1e47ac0c908febf4f3a2bd10c8066140f741261",
					URL:     "https://example.com/elcapitan/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version.major}/appcast.xml",
						Checkpoint: "57956bd3fb23a5673e30dc83ed19d51b43e5a9235756887f3ed90662e6c68fb7",
						global:     false,
					},
					Names: []string{
						"Example",
						"Example (if-six-versions-six-appcasts)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-six-versions-six-appcasts).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
			},
		},
		"if-three-versions-one-appcast.rb": Cask{
			Token:   "if-three-versions-one-appcast",
			Content: string(getTestdata("if-three-versions-one-appcast.rb")),
			Variants: []Variant{
				{
					Version: "0.9.0",
					SHA256:  "30c99e8b103eacbe6f6d6e1b54b06ca6d5f3164b4f50094334a517ae95ca8fba",
					URL:     "https://example.com/app_#{version}.dmg",
					Appcast: Appcast{},
					Names: []string{
						"Example",
						"Example (if-three-versions-one-appcast)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-three-versions-one-appcast).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
				{
					Version: "1.0.0",
					SHA256:  "92521fc3cbd964bdc9f584a991b89fddaa5754ed1cc96d6d42445338669c1305",
					URL:     "https://example.com/app_#{version}.dmg",
					Appcast: Appcast{},
					Names: []string{
						"Example",
						"Example (if-three-versions-one-appcast)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-three-versions-one-appcast).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
				{
					Version: "2.0.0",
					SHA256:  "f22abd6773ab232869321ad4b1e47ac0c908febf4f3a2bd10c8066140f741261",
					URL:     "https://example.com/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version.major}/appcast.xml",
						Checkpoint: "8dc47a4bcec6e46b79fb6fc7b84224f1461f18a2d9f2e5adc94612bb9b97072d",
						global:     false,
					},
					Names: []string{
						"Example",
						"Example (if-three-versions-one-appcast)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-three-versions-one-appcast).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
			},
		},
		"if-two-versions-one-global-appcast.rb": Cask{
			Token:   "if-two-versions-one-global-appcast",
			Content: string(getTestdata("if-two-versions-one-global-appcast.rb")),
			Variants: []Variant{
				{
					Version: "1.0.0",
					SHA256:  "92521fc3cbd964bdc9f584a991b89fddaa5754ed1cc96d6d42445338669c1305",
					URL:     "https://example.com/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version.major}/appcast.xml",
						Checkpoint: "8dc47a4bcec6e46b79fb6fc7b84224f1461f18a2d9f2e5adc94612bb9b97072d",
						global:     true,
					},
					Names: []string{
						"Example",
						"Example (if-two-versions-one-global-appcast)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-two-versions-one-global-appcast).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
				{
					Version: "2.0.0",
					SHA256:  "f22abd6773ab232869321ad4b1e47ac0c908febf4f3a2bd10c8066140f741261",
					URL:     "https://example.com/app_#{version}.dmg",
					Appcast: Appcast{
						URL:        "https://example.com/sparkle/#{version.major}/appcast.xml",
						Checkpoint: "8dc47a4bcec6e46b79fb6fc7b84224f1461f18a2d9f2e5adc94612bb9b97072d",
						global:     true,
					},
					Names: []string{
						"Example",
						"Example (if-two-versions-one-global-appcast)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (if-two-versions-one-global-appcast).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-if",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
			},
		},
		"latest.rb": {
			Token:   "latest",
			Content: string(getTestdata("latest.rb")),
			Variants: []Variant{
				{
					Version: "latest",
					SHA256:  "5e1e2bcac305958b27077ca136f35f0abae7cf38c9af678f7d220ed0cb51d4f8",
					URL:     "https://example.com/app_#{version}.dmg",
					Appcast: Appcast{},
					Names: []string{
						"Example",
						"Example (latest)",
					},
					Homepage: "https://example.com/",
					Artifacts: []Artifact{
						{
							Type:           ArtifactApp,
							Value:          "Example (latest).app",
							Target:         "Example.app",
							AllowUntrusted: false,
						},
						{
							Type:           ArtifactBinary,
							Value:          "#{appdir}/Example.app/Contents/MacOS/example-latest",
							Target:         "example",
							AllowUntrusted: false,
						},
					},
				},
			},
		},
	}

	for filename, expectedCask := range testCases {
		// preparations
		actualCask := NewCask(string(getTestdata(filename)))
		err := actualCask.Parse()

		// test
		assert.IsType(t, Cask{}, *actualCask)
		assert.Nil(t, err)
		assert.Equal(t, expectedCask.Token, actualCask.Token, fmt.Sprintf(
			"%s: expected and actual cask tokens doesn't match",
			filename,
		))
		assert.Equal(t, expectedCask.Content, actualCask.Content)

		// variants
		assert.Len(t, actualCask.Variants, len(expectedCask.Variants), fmt.Sprintf(
			"%s: expected (%d) and actual (%d) cask variants length doesn't match",
			filename,
			len(expectedCask.Variants),
			len(actualCask.Variants),
		))

		if len(expectedCask.Variants) == len(actualCask.Variants) {
			for key, actualVariant := range actualCask.Variants {
				expectedVariant := expectedCask.Variants[key]
				assert.IsType(t, Variant{}, actualVariant)
				assert.Equal(t, expectedVariant.Version, actualVariant.Version)
				assert.Equal(t, expectedVariant.SHA256, actualVariant.SHA256)
				assert.Equal(t, expectedVariant.URL, actualVariant.URL)
				assert.Equal(t, expectedVariant.Names, actualVariant.Names)
				assert.Equal(t, expectedVariant.Homepage, actualVariant.Homepage)
				assert.Equal(t, expectedVariant.Appcast, actualVariant.Appcast)
				assert.Equal(t, expectedVariant.Artifacts, actualVariant.Artifacts)
			}
		}
	}
}

func TestAddVariant(t *testing.T) {
	// preparations
	c := NewCask("")

	// test
	assert.Len(t, c.Variants, 0)
	c.AddVariant(*NewVariant())
	assert.Len(t, c.Variants, 1)
}

func TestCaskString(t *testing.T) {
	// preparations
	c := NewCask("")
	c.Token = "test"

	// test
	assert.Equal(t, "test", c.String())
}
<|endoftext|>


<commit_before>/*
Copyright 2017 wechat-go Authors. All Rights Reserved.
MIT License

Copyright (c) 2017

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package wxbot

import (
	"encoding/json"
	"fmt"
	"github.com/songtianyi/wechat-go/wxweb"
)

type MemberManager struct {
	Group *wxweb.User
}

func CreateMemberManagerFromGroupContact(user *wxweb.User) (*MemberManager, error) {
	b, err := wxweb.WebWxBatchGetContact(WxWebDefaultCommon, WxWebXcg, Cookies, []*wxweb.User{user})
	if err != nil {
		return nil, err
	}
	return CreateMemberManagerFromBytes(b)
}

func CreateMemberManagerFromBytes(b []byte) (*MemberManager, error) {
	var gcr wxweb.GroupContactResponse
	if err := json.Unmarshal(b, &gcr); err != nil {
		return nil, err
	}
	if gcr.BaseResponse.Ret != 0 {
		return nil, fmt.Errorf("WebWxBatchGetContact ret=%d", gcr.BaseResponse.Ret)
	}

	if gcr.ContactList == nil || len(gcr.ContactList) < 1 {
		return nil, fmt.Errorf("ContactList empty")
	}

	mm := &MemberManager{
		Group: gcr.ContactList[0],
	}
	return mm, nil
}

func (s *MemberManager) Update() error {
	members := make([]*wxweb.User, len(s.Group.MemberList))
	for i, v := range s.Group.MemberList {
		members[i] = &wxweb.User{
			UserName:        v.UserName,
			EncryChatRoomId: s.Group.UserName,
		}
	}
	b, err := wxweb.WebWxBatchGetContact(WxWebDefaultCommon, WxWebXcg, Cookies, members)
	if err != nil {
		return err
	}

	var gcr wxweb.GroupContactResponse
	if err := json.Unmarshal(b, &gcr); err != nil {
		return err
	}
	s.Group.MemberList = gcr.ContactList
	fmt.Println(s.Group.NickName)
	for _, v := range s.Group.MemberList {
		fmt.Println(v.NickName)
	}
	return nil
}

func (s *MemberManager) GetHeadImgUrlByGender(sex int) []string {
	uris := make([]string, 0)
	for _, v := range s.Group.MemberList {
		if v.Sex == sex {
			uris = append(uris, v.HeadImgUrl)
		}
	}
	return uris
}

func (s *MemberManager) GetContactsByGender(sex int) []*wxweb.User {
	contacts := make([]*wxweb.User, 0)
	for _, v := range s.Group.MemberList {
		if v.Sex == sex {
			contacts = append(contacts, v)
		}
	}
	return contacts
}
<commit_msg>member add GetContactsByUserName<commit_after>/*
Copyright 2017 wechat-go Authors. All Rights Reserved.
MIT License

Copyright (c) 2017

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package wxbot

import (
	"encoding/json"
	"fmt"
	"github.com/songtianyi/wechat-go/wxweb"
)

type MemberManager struct {
	Group *wxweb.User
}

func CreateMemberManagerFromGroupContact(user *wxweb.User) (*MemberManager, error) {
	b, err := wxweb.WebWxBatchGetContact(WxWebDefaultCommon, WxWebXcg, Cookies, []*wxweb.User{user})
	if err != nil {
		return nil, err
	}
	return CreateMemberManagerFromBytes(b)
}

func CreateMemberManagerFromBytes(b []byte) (*MemberManager, error) {
	var gcr wxweb.GroupContactResponse
	if err := json.Unmarshal(b, &gcr); err != nil {
		return nil, err
	}
	if gcr.BaseResponse.Ret != 0 {
		return nil, fmt.Errorf("WebWxBatchGetContact ret=%d", gcr.BaseResponse.Ret)
	}

	if gcr.ContactList == nil || len(gcr.ContactList) < 1 {
		return nil, fmt.Errorf("ContactList empty")
	}

	mm := &MemberManager{
		Group: gcr.ContactList[0],
	}
	return mm, nil
}

func (s *MemberManager) Update() error {
	members := make([]*wxweb.User, len(s.Group.MemberList))
	for i, v := range s.Group.MemberList {
		members[i] = &wxweb.User{
			UserName:        v.UserName,
			EncryChatRoomId: s.Group.UserName,
		}
	}
	b, err := wxweb.WebWxBatchGetContact(WxWebDefaultCommon, WxWebXcg, Cookies, members)
	if err != nil {
		return err
	}

	var gcr wxweb.GroupContactResponse
	if err := json.Unmarshal(b, &gcr); err != nil {
		return err
	}
	s.Group.MemberList = gcr.ContactList
	fmt.Println(s.Group.NickName)
	for _, v := range s.Group.MemberList {
		fmt.Println(v.NickName)
	}
	return nil
}

func (s *MemberManager) GetHeadImgUrlByGender(sex int) []string {
	uris := make([]string, 0)
	for _, v := range s.Group.MemberList {
		if v.Sex == sex {
			uris = append(uris, v.HeadImgUrl)
		}
	}
	return uris
}

func (s *MemberManager) GetContactsByGender(sex int) []*wxweb.User {
	contacts := make([]*wxweb.User, 0)
	for _, v := range s.Group.MemberList {
		if v.Sex == sex {
			contacts = append(contacts, v)
		}
	}
	return contacts
}

func (s *MemberManager) GetContactsByUserName(username string) *wxweb.User {
	for _, v := range s.Group.MemberList {
		if v.UserName == username {
			return v
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package gokvstores

import (
	"time"

	"github.com/patrickmn/go-cache"
)

// MemoryStore is the in-memory implementation of KVStore.
type MemoryStore struct {
	cache           *cache.Cache
	expiration      time.Duration
	cleanupInterval time.Duration
}

// Get returns item from the cache.
func (c *MemoryStore) Get(key string) (interface{}, error) {
	item, _ := c.cache.Get(key)
	return item, nil
}

// MGet returns map of key, value for a list of keys.
func (c *MemoryStore) MGet(keys []string) (map[string]interface{}, error) {
	results := make(map[string]interface{}, len(keys))
	for _, key := range keys {
		item, _ := c.Get(key)
		results[key] = item
	}
	return results, nil
}

// Set sets value in the cache.
func (c *MemoryStore) Set(key string, value interface{}) error {
	c.cache.Set(key, value, c.expiration)
	return nil
}

// SetWithExpiration sets the value for the given key for a specified duration.
func (c *MemoryStore) SetWithExpiration(key string, value interface{}, expiration time.Duration) error {
	c.cache.Set(key, value, expiration)
	return nil
}

// GetMap returns map for the given key.
func (c *MemoryStore) GetMap(key string) (map[string]interface{}, error) {
	if v, found := c.cache.Get(key); found {
		return v.(map[string]interface{}), nil
	}
	return nil, nil
}

// GetMaps returns maps for the given keys.
func (c *MemoryStore) GetMaps(keys []string) (map[string]map[string]interface{}, error) {
	values := make(map[string]map[string]interface{}, len(keys))
	for _, v := range keys {
		value, _ := c.GetMap(v)
		if value != nil {
			values[v] = value
		}
	}

	return values, nil
}

// SetMap sets a map for the given key.
func (c *MemoryStore) SetMap(key string, value map[string]interface{}) error {
	c.cache.Set(key, value, c.expiration)
	return nil
}

// DeleteMap removes the specified fields from the map stored at key.
func (c *MemoryStore) DeleteMap(key string, fields ...string) error {
	m, err := c.GetMap(key)
	if err != nil {
		return err
	}

	for _, field := range fields {
		delete(m, field)
	}

	return c.SetMap(key, m)
}

// GetSlice returns slice for the given key.
func (c *MemoryStore) GetSlice(key string) ([]interface{}, error) {
	if v, found := c.cache.Get(key); found {
		return v.([]interface{}), nil
	}
	return nil, nil
}

// SetSlice sets slice for the given key.
func (c *MemoryStore) SetSlice(key string, value []interface{}) error {
	c.cache.Set(key, value, c.expiration)
	return nil
}

// AppendSlice appends values to the given slice.
func (c *MemoryStore) AppendSlice(key string, values ...interface{}) error {
	items, err := c.GetSlice(key)
	if err != nil {
		return err
	}

	for _, item := range values {
		items = append(items, item)
	}

	return c.cache.Replace(key, items, c.expiration)
}

// Close does nothing for this backend.
func (c *MemoryStore) Close() error {
	return nil
}

// Flush removes all items from the cache.
func (c *MemoryStore) Flush() error {
	c.cache.Flush()
	return nil
}

// Delete deletes the given key.
func (c *MemoryStore) Delete(key string) error {
	c.cache.Delete(key)
	return nil
}

// Keys returns all keys matching pattern
func (c *MemoryStore) Keys(pattern string) ([]interface{}, error) {
	return nil, nil
}

// Exists checks if the given key exists.
func (c *MemoryStore) Exists(key string) (bool, error) {
	if _, exists := c.cache.Get(key); exists {
		return true, nil
	}
	return false, nil
}

// NewMemoryStore returns in-memory KVStore.
func NewMemoryStore(expiration time.Duration, cleanupInterval time.Duration) (KVStore, error) {
	return &MemoryStore{
		cache:           cache.New(expiration, cleanupInterval),
		expiration:      time.Duration(expiration) * time.Second,
		cleanupInterval: cleanupInterval,
	}, nil
}
<commit_msg>fix: MemoryStore.AppendSlice should not panic if key not found<commit_after>package gokvstores

import (
	"time"

	"github.com/patrickmn/go-cache"
)

// MemoryStore is the in-memory implementation of KVStore.
type MemoryStore struct {
	cache           *cache.Cache
	expiration      time.Duration
	cleanupInterval time.Duration
}

// Get returns item from the cache.
func (c *MemoryStore) Get(key string) (interface{}, error) {
	item, _ := c.cache.Get(key)
	return item, nil
}

// MGet returns map of key, value for a list of keys.
func (c *MemoryStore) MGet(keys []string) (map[string]interface{}, error) {
	results := make(map[string]interface{}, len(keys))
	for _, key := range keys {
		item, _ := c.Get(key)
		results[key] = item
	}
	return results, nil
}

// Set sets value in the cache.
func (c *MemoryStore) Set(key string, value interface{}) error {
	c.cache.Set(key, value, c.expiration)
	return nil
}

// SetWithExpiration sets the value for the given key for a specified duration.
func (c *MemoryStore) SetWithExpiration(key string, value interface{}, expiration time.Duration) error {
	c.cache.Set(key, value, expiration)
	return nil
}

// GetMap returns map for the given key.
func (c *MemoryStore) GetMap(key string) (map[string]interface{}, error) {
	if v, found := c.cache.Get(key); found {
		return v.(map[string]interface{}), nil
	}
	return nil, nil
}

// GetMaps returns maps for the given keys.
func (c *MemoryStore) GetMaps(keys []string) (map[string]map[string]interface{}, error) {
	values := make(map[string]map[string]interface{}, len(keys))
	for _, v := range keys {
		value, _ := c.GetMap(v)
		if value != nil {
			values[v] = value
		}
	}

	return values, nil
}

// SetMap sets a map for the given key.
func (c *MemoryStore) SetMap(key string, value map[string]interface{}) error {
	c.cache.Set(key, value, c.expiration)
	return nil
}

// DeleteMap removes the specified fields from the map stored at key.
func (c *MemoryStore) DeleteMap(key string, fields ...string) error {
	m, err := c.GetMap(key)
	if err != nil {
		return err
	}

	for _, field := range fields {
		delete(m, field)
	}

	return c.SetMap(key, m)
}

// GetSlice returns slice for the given key.
func (c *MemoryStore) GetSlice(key string) ([]interface{}, error) {
	if v, found := c.cache.Get(key); found {
		return v.([]interface{}), nil
	}
	return nil, nil
}

// SetSlice sets slice for the given key.
func (c *MemoryStore) SetSlice(key string, value []interface{}) error {
	c.cache.Set(key, value, c.expiration)
	return nil
}

// AppendSlice appends values to the given slice.
func (c *MemoryStore) AppendSlice(key string, values ...interface{}) error {
	items, err := c.GetSlice(key)
	if err != nil {
		return err
	}

	if items == nil {
		return c.SetSlice(key, values)
	}

	for _, item := range values {
		items = append(items, item)
	}

	return c.cache.Replace(key, items, c.expiration)
}

// Close does nothing for this backend.
func (c *MemoryStore) Close() error {
	return nil
}

// Flush removes all items from the cache.
func (c *MemoryStore) Flush() error {
	c.cache.Flush()
	return nil
}

// Delete deletes the given key.
func (c *MemoryStore) Delete(key string) error {
	c.cache.Delete(key)
	return nil
}

// Keys returns all keys matching pattern
func (c *MemoryStore) Keys(pattern string) ([]interface{}, error) {
	return nil, nil
}

// Exists checks if the given key exists.
func (c *MemoryStore) Exists(key string) (bool, error) {
	if _, exists := c.cache.Get(key); exists {
		return true, nil
	}
	return false, nil
}

// NewMemoryStore returns in-memory KVStore.
func NewMemoryStore(expiration time.Duration, cleanupInterval time.Duration) (KVStore, error) {
	return &MemoryStore{
		cache:           cache.New(expiration, cleanupInterval),
		expiration:      time.Duration(expiration) * time.Second,
		cleanupInterval: cleanupInterval,
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2018 The Ebiten 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.

package ebiten

import (
	"fmt"
	"image"
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/buffered"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/graphics"
)

type levelToImage map[int]*buffered.Image

type mipmap struct {
	width    int
	height   int
	volatile bool
	orig     *buffered.Image
	imgs     map[image.Rectangle]levelToImage
}

func newMipmap(width, height int, volatile bool) *mipmap {
	return &mipmap{
		width:    width,
		height:   height,
		volatile: volatile,
		orig:     buffered.NewImage(width, height, volatile),
		imgs:     map[image.Rectangle]levelToImage{},
	}
}

func newScreenFramebufferMipmap(width, height int) *mipmap {
	return &mipmap{
		width:  width,
		height: height,
		orig:   buffered.NewScreenFramebufferImage(width, height),
		imgs:   map[image.Rectangle]levelToImage{},
	}
}

func (m *mipmap) dump(name string) error {
	return m.orig.Dump(name)
}

func (m *mipmap) fill(clr color.RGBA) {
	m.orig.Fill(clr)
	m.disposeMipmaps()
}

func (m *mipmap) replacePixels(pix []byte) {
	m.orig.ReplacePixels(pix)
	m.disposeMipmaps()
}

func (m *mipmap) size() (int, int) {
	return m.width, m.height
}

func (m *mipmap) at(x, y int) (r, g, b, a byte) {
	return m.orig.At(x, y)
}

func (m *mipmap) drawImage(src *mipmap, bounds image.Rectangle, geom *GeoM, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
	if det := geom.det(); det == 0 {
		return
	} else if math.IsNaN(float64(det)) {
		return
	}

	level := src.mipmapLevel(geom, bounds.Dx(), bounds.Dy(), filter)

	if level > 0 {
		// If the image can be scaled into 0 size, adjust the level. (#839)
		w, h := bounds.Dx(), bounds.Dy()
		for level >= 0 {
			s := 1 << uint(level)
			if w/s == 0 || h/s == 0 {
				level--
				continue
			}
			break
		}

		if level < 0 {
			// As the render source is too small, nothing is rendered.
			return
		}
	}

	if level > 6 {
		level = 6
	}
	if level < -6 {
		level = -6
	}

	cr, cg, cb, ca := float32(1), float32(1), float32(1), float32(1)
	if colorm.ScaleOnly() {
		body, _ := colorm.UnsafeElements()
		cr = body[0]
		cg = body[5]
		cb = body[10]
		ca = body[15]
		colorm = nil
	}

	screen := filter == driver.FilterScreen
	if screen && level != 0 {
		panic("ebiten: Mipmap must not be used when the filter is FilterScreen")
	}

	a, b, c, d, tx, ty := geom.elements()
	if level == 0 {
		vs := quadVertices(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, a, b, c, d, tx, ty, cr, cg, cb, ca, screen)
		is := graphics.QuadIndices()
		m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, driver.AddressClampToZero)
	} else if buf := src.level(bounds, level); buf != nil {
		w, h := sizeForLevel(bounds.Dx(), bounds.Dy(), level)
		s := pow2(level)
		a *= s
		b *= s
		c *= s
		d *= s
		vs := quadVertices(0, 0, w, h, a, b, c, d, tx, ty, cr, cg, cb, ca, false)
		is := graphics.QuadIndices()
		m.orig.DrawTriangles(buf, vs, is, colorm, mode, filter, driver.AddressClampToZero)
	}
	m.disposeMipmaps()
}

func (m *mipmap) drawTriangles(src *mipmap, bounds image.Rectangle, vertices []Vertex, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) {
	bx0 := float32(bounds.Min.X)
	by0 := float32(bounds.Min.Y)
	bx1 := float32(bounds.Max.X)
	by1 := float32(bounds.Max.Y)

	// TODO: Needs boundary check optimization?
	// See https://go101.org/article/bounds-check-elimination.html

	vs := vertexSlice(len(vertices), false)
	for i, v := range vertices {
		vs[i*graphics.VertexFloatNum] = v.DstX
		vs[i*graphics.VertexFloatNum+1] = v.DstY
		vs[i*graphics.VertexFloatNum+2] = v.SrcX
		vs[i*graphics.VertexFloatNum+3] = v.SrcY
		vs[i*graphics.VertexFloatNum+4] = bx0
		vs[i*graphics.VertexFloatNum+5] = by0
		vs[i*graphics.VertexFloatNum+6] = bx1
		vs[i*graphics.VertexFloatNum+7] = by1
		vs[i*graphics.VertexFloatNum+8] = v.ColorR
		vs[i*graphics.VertexFloatNum+9] = v.ColorG
		vs[i*graphics.VertexFloatNum+10] = v.ColorB
		vs[i*graphics.VertexFloatNum+11] = v.ColorA
	}
	is := make([]uint16, len(indices))
	copy(is, indices)
	m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, address)
	m.disposeMipmaps()
}

func (m *mipmap) level(r image.Rectangle, level int) *buffered.Image {
	if level == 0 {
		panic("ebiten: level must be non-zero at level")
	}

	if m.volatile {
		panic("ebiten: mipmap images for a volatile image is not implemented yet")
	}

	if _, ok := m.imgs[r]; !ok {
		m.imgs[r] = levelToImage{}
	}
	imgs := m.imgs[r]

	if img, ok := imgs[level]; ok {
		return img
	}

	var src *buffered.Image
	var vs []float32
	var filter driver.Filter
	switch {
	case level == 1:
		src = m.orig
		vs = quadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterLinear
	case level > 1:
		src = m.level(r, level-1)
		if src == nil {
			imgs[level] = nil
			return nil
		}
		w, h := sizeForLevel(r.Dx(), r.Dy(), level-1)
		vs = quadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterLinear
	case level == -1:
		src = m.orig
		vs = quadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterNearest
	case level < -1:
		src = m.level(r, level+1)
		if src == nil {
			imgs[level] = nil
			return nil
		}
		w, h := sizeForLevel(r.Dx(), r.Dy(), level+1)
		vs = quadVertices(0, 0, w, h, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterNearest
	default:
		panic(fmt.Sprintf("ebiten: invalid level: %d", level))
	}
	is := graphics.QuadIndices()

	w2, h2 := sizeForLevel(r.Dx(), r.Dy(), level)
	if w2 == 0 || h2 == 0 {
		imgs[level] = nil
		return nil
	}
	s := buffered.NewImage(w2, h2, m.volatile)
	s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressClampToZero)
	imgs[level] = s

	return imgs[level]
}

func sizeForLevel(origWidth, origHeight int, level int) (width, height int) {
	width = origWidth
	height = origHeight
	if level > 0 {
		for i := 0; i < level; i++ {
			width /= 2
			height /= 2
			if width == 0 || height == 0 {
				return 0, 0
			}
		}
	} else {
		for i := 0; i < -level; i++ {
			width *= 2
			height *= 2
		}
	}
	return
}

func (m *mipmap) isDisposed() bool {
	return m.orig == nil
}

func (m *mipmap) dispose() {
	m.disposeMipmaps()
	m.orig.MarkDisposed()
	m.orig = nil
}

func (m *mipmap) disposeMipmaps() {
	for _, a := range m.imgs {
		for _, img := range a {
			img.MarkDisposed()
		}
	}
	for k := range m.imgs {
		delete(m.imgs, k)
	}
}

func (m *mipmap) clearFramebuffer() {
	m.orig.ClearFramebuffer()
}

// mipmapLevel returns an appropriate mipmap level for the given determinant of a geometry matrix.
//
// mipmapLevel panics if det is NaN or 0.
func (m *mipmap) mipmapLevel(geom *GeoM, width, height int, filter driver.Filter) int {
	det := geom.det()
	if math.IsNaN(float64(det)) {
		panic("ebiten: det must be finite at mipmapLevel")
	}
	if det == 0 {
		panic("ebiten: dst must be non zero at mipmapLevel")
	}

	if filter == driver.FilterScreen {
		return 0
	}

	// Use 'negative' mipmap to render edges correctly (#611, #907).
	// It looks like 128 is the enlargement factor that causes edge missings to pass the test TestImageStretch.
	const tooBigScale = 128
	if sx, sy := geomScaleSize(geom); sx >= tooBigScale || sy >= tooBigScale {
		// If the filter is not nearest, the target needs to be rendered with graduation. Don't use mipmaps.
		if filter != driver.FilterNearest {
			return 0
		}

		const mipmapMaxSize = 1024
		w, h := width, height
		if w >= mipmapMaxSize || h >= mipmapMaxSize {
			return 0
		}

		level := 0
		for sx >= tooBigScale || sy >= tooBigScale {
			level--
			sx /= 2
			sy /= 2
			w *= 2
			h *= 2
			if w >= mipmapMaxSize || h >= mipmapMaxSize {
				break
			}
		}
		return level
	}

	if filter != driver.FilterLinear {
		return 0
	}
	if m.volatile {
		return 0
	}

	// This is a separate function for testing.
	return mipmapLevelForDownscale(det)
}

func mipmapLevelForDownscale(det float32) int {
	if math.IsNaN(float64(det)) {
		panic("ebiten: det must be finite at mipmapLevelForDownscale")
	}
	if det == 0 {
		panic("ebiten: dst must be non zero at mipmapLevelForDownscale")
	}

	// TODO: Should this be determined by x/y scales instead of det?
	d := math.Abs(float64(det))
	level := 0
	for d < 0.25 {
		level++
		d *= 4
	}
	return level
}

func pow2(power int) float32 {
	if power >= 0 {
		x := 1
		return float32(x << uint(power))
	}

	x := float32(1)
	for i := 0; i < -power; i++ {
		x /= 2
	}
	return x
}

func maxf32(values ...float32) float32 {
	max := float32(math.Inf(-1))
	for _, v := range values {
		if max < v {
			max = v
		}
	}
	return max
}

func minf32(values ...float32) float32 {
	min := float32(math.Inf(1))
	for _, v := range values {
		if min > v {
			min = v
		}
	}
	return min
}

func geomScaleSize(geom *GeoM) (sx, sy float32) {
	a, b, c, d, _, _ := geom.elements()
	// (0, 1)
	x0 := 0*a + 1*b
	y0 := 0*c + 1*d

	// (1, 0)
	x1 := 1*a + 0*b
	y1 := 1*c + 0*d

	// (1, 1)
	x2 := 1*a + 1*b
	y2 := 1*c + 1*d

	maxx := maxf32(0, x0, x1, x2)
	maxy := maxf32(0, y0, y1, y2)
	minx := minf32(0, x0, x1, x2)
	miny := minf32(0, y0, y1, y2)

	return maxx - minx, maxy - miny
}
<commit_msg>graphics: Speed optimization at maxf32 and minf32<commit_after>// Copyright 2018 The Ebiten 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.

package ebiten

import (
	"fmt"
	"image"
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/buffered"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/graphics"
)

type levelToImage map[int]*buffered.Image

type mipmap struct {
	width    int
	height   int
	volatile bool
	orig     *buffered.Image
	imgs     map[image.Rectangle]levelToImage
}

func newMipmap(width, height int, volatile bool) *mipmap {
	return &mipmap{
		width:    width,
		height:   height,
		volatile: volatile,
		orig:     buffered.NewImage(width, height, volatile),
		imgs:     map[image.Rectangle]levelToImage{},
	}
}

func newScreenFramebufferMipmap(width, height int) *mipmap {
	return &mipmap{
		width:  width,
		height: height,
		orig:   buffered.NewScreenFramebufferImage(width, height),
		imgs:   map[image.Rectangle]levelToImage{},
	}
}

func (m *mipmap) dump(name string) error {
	return m.orig.Dump(name)
}

func (m *mipmap) fill(clr color.RGBA) {
	m.orig.Fill(clr)
	m.disposeMipmaps()
}

func (m *mipmap) replacePixels(pix []byte) {
	m.orig.ReplacePixels(pix)
	m.disposeMipmaps()
}

func (m *mipmap) size() (int, int) {
	return m.width, m.height
}

func (m *mipmap) at(x, y int) (r, g, b, a byte) {
	return m.orig.At(x, y)
}

func (m *mipmap) drawImage(src *mipmap, bounds image.Rectangle, geom *GeoM, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
	if det := geom.det(); det == 0 {
		return
	} else if math.IsNaN(float64(det)) {
		return
	}

	level := src.mipmapLevel(geom, bounds.Dx(), bounds.Dy(), filter)

	if level > 0 {
		// If the image can be scaled into 0 size, adjust the level. (#839)
		w, h := bounds.Dx(), bounds.Dy()
		for level >= 0 {
			s := 1 << uint(level)
			if w/s == 0 || h/s == 0 {
				level--
				continue
			}
			break
		}

		if level < 0 {
			// As the render source is too small, nothing is rendered.
			return
		}
	}

	if level > 6 {
		level = 6
	}
	if level < -6 {
		level = -6
	}

	cr, cg, cb, ca := float32(1), float32(1), float32(1), float32(1)
	if colorm.ScaleOnly() {
		body, _ := colorm.UnsafeElements()
		cr = body[0]
		cg = body[5]
		cb = body[10]
		ca = body[15]
		colorm = nil
	}

	screen := filter == driver.FilterScreen
	if screen && level != 0 {
		panic("ebiten: Mipmap must not be used when the filter is FilterScreen")
	}

	a, b, c, d, tx, ty := geom.elements()
	if level == 0 {
		vs := quadVertices(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, a, b, c, d, tx, ty, cr, cg, cb, ca, screen)
		is := graphics.QuadIndices()
		m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, driver.AddressClampToZero)
	} else if buf := src.level(bounds, level); buf != nil {
		w, h := sizeForLevel(bounds.Dx(), bounds.Dy(), level)
		s := pow2(level)
		a *= s
		b *= s
		c *= s
		d *= s
		vs := quadVertices(0, 0, w, h, a, b, c, d, tx, ty, cr, cg, cb, ca, false)
		is := graphics.QuadIndices()
		m.orig.DrawTriangles(buf, vs, is, colorm, mode, filter, driver.AddressClampToZero)
	}
	m.disposeMipmaps()
}

func (m *mipmap) drawTriangles(src *mipmap, bounds image.Rectangle, vertices []Vertex, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) {
	bx0 := float32(bounds.Min.X)
	by0 := float32(bounds.Min.Y)
	bx1 := float32(bounds.Max.X)
	by1 := float32(bounds.Max.Y)

	// TODO: Needs boundary check optimization?
	// See https://go101.org/article/bounds-check-elimination.html

	vs := vertexSlice(len(vertices), false)
	for i, v := range vertices {
		vs[i*graphics.VertexFloatNum] = v.DstX
		vs[i*graphics.VertexFloatNum+1] = v.DstY
		vs[i*graphics.VertexFloatNum+2] = v.SrcX
		vs[i*graphics.VertexFloatNum+3] = v.SrcY
		vs[i*graphics.VertexFloatNum+4] = bx0
		vs[i*graphics.VertexFloatNum+5] = by0
		vs[i*graphics.VertexFloatNum+6] = bx1
		vs[i*graphics.VertexFloatNum+7] = by1
		vs[i*graphics.VertexFloatNum+8] = v.ColorR
		vs[i*graphics.VertexFloatNum+9] = v.ColorG
		vs[i*graphics.VertexFloatNum+10] = v.ColorB
		vs[i*graphics.VertexFloatNum+11] = v.ColorA
	}
	is := make([]uint16, len(indices))
	copy(is, indices)
	m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, address)
	m.disposeMipmaps()
}

func (m *mipmap) level(r image.Rectangle, level int) *buffered.Image {
	if level == 0 {
		panic("ebiten: level must be non-zero at level")
	}

	if m.volatile {
		panic("ebiten: mipmap images for a volatile image is not implemented yet")
	}

	if _, ok := m.imgs[r]; !ok {
		m.imgs[r] = levelToImage{}
	}
	imgs := m.imgs[r]

	if img, ok := imgs[level]; ok {
		return img
	}

	var src *buffered.Image
	var vs []float32
	var filter driver.Filter
	switch {
	case level == 1:
		src = m.orig
		vs = quadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterLinear
	case level > 1:
		src = m.level(r, level-1)
		if src == nil {
			imgs[level] = nil
			return nil
		}
		w, h := sizeForLevel(r.Dx(), r.Dy(), level-1)
		vs = quadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterLinear
	case level == -1:
		src = m.orig
		vs = quadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterNearest
	case level < -1:
		src = m.level(r, level+1)
		if src == nil {
			imgs[level] = nil
			return nil
		}
		w, h := sizeForLevel(r.Dx(), r.Dy(), level+1)
		vs = quadVertices(0, 0, w, h, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterNearest
	default:
		panic(fmt.Sprintf("ebiten: invalid level: %d", level))
	}
	is := graphics.QuadIndices()

	w2, h2 := sizeForLevel(r.Dx(), r.Dy(), level)
	if w2 == 0 || h2 == 0 {
		imgs[level] = nil
		return nil
	}
	s := buffered.NewImage(w2, h2, m.volatile)
	s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressClampToZero)
	imgs[level] = s

	return imgs[level]
}

func sizeForLevel(origWidth, origHeight int, level int) (width, height int) {
	width = origWidth
	height = origHeight
	if level > 0 {
		for i := 0; i < level; i++ {
			width /= 2
			height /= 2
			if width == 0 || height == 0 {
				return 0, 0
			}
		}
	} else {
		for i := 0; i < -level; i++ {
			width *= 2
			height *= 2
		}
	}
	return
}

func (m *mipmap) isDisposed() bool {
	return m.orig == nil
}

func (m *mipmap) dispose() {
	m.disposeMipmaps()
	m.orig.MarkDisposed()
	m.orig = nil
}

func (m *mipmap) disposeMipmaps() {
	for _, a := range m.imgs {
		for _, img := range a {
			img.MarkDisposed()
		}
	}
	for k := range m.imgs {
		delete(m.imgs, k)
	}
}

func (m *mipmap) clearFramebuffer() {
	m.orig.ClearFramebuffer()
}

// mipmapLevel returns an appropriate mipmap level for the given determinant of a geometry matrix.
//
// mipmapLevel panics if det is NaN or 0.
func (m *mipmap) mipmapLevel(geom *GeoM, width, height int, filter driver.Filter) int {
	det := geom.det()
	if math.IsNaN(float64(det)) {
		panic("ebiten: det must be finite at mipmapLevel")
	}
	if det == 0 {
		panic("ebiten: dst must be non zero at mipmapLevel")
	}

	if filter == driver.FilterScreen {
		return 0
	}

	// Use 'negative' mipmap to render edges correctly (#611, #907).
	// It looks like 128 is the enlargement factor that causes edge missings to pass the test TestImageStretch.
	const tooBigScale = 128
	if sx, sy := geomScaleSize(geom); sx >= tooBigScale || sy >= tooBigScale {
		// If the filter is not nearest, the target needs to be rendered with graduation. Don't use mipmaps.
		if filter != driver.FilterNearest {
			return 0
		}

		const mipmapMaxSize = 1024
		w, h := width, height
		if w >= mipmapMaxSize || h >= mipmapMaxSize {
			return 0
		}

		level := 0
		for sx >= tooBigScale || sy >= tooBigScale {
			level--
			sx /= 2
			sy /= 2
			w *= 2
			h *= 2
			if w >= mipmapMaxSize || h >= mipmapMaxSize {
				break
			}
		}
		return level
	}

	if filter != driver.FilterLinear {
		return 0
	}
	if m.volatile {
		return 0
	}

	// This is a separate function for testing.
	return mipmapLevelForDownscale(det)
}

func mipmapLevelForDownscale(det float32) int {
	if math.IsNaN(float64(det)) {
		panic("ebiten: det must be finite at mipmapLevelForDownscale")
	}
	if det == 0 {
		panic("ebiten: dst must be non zero at mipmapLevelForDownscale")
	}

	// TODO: Should this be determined by x/y scales instead of det?
	d := math.Abs(float64(det))
	level := 0
	for d < 0.25 {
		level++
		d *= 4
	}
	return level
}

func pow2(power int) float32 {
	if power >= 0 {
		x := 1
		return float32(x << uint(power))
	}

	x := float32(1)
	for i := 0; i < -power; i++ {
		x /= 2
	}
	return x
}

func maxf32(a, b, c, d float32) float32 {
	max := a
	if max < b {
		max = b
	}
	if max < c {
		max = c
	}
	if max < d {
		max = d
	}
	return max
}

func minf32(a, b, c, d float32) float32 {
	min := a
	if min > b {
		min = b
	}
	if min > c {
		min = c
	}
	if min > d {
		min = d
	}
	return min
}

func geomScaleSize(geom *GeoM) (sx, sy float32) {
	a, b, c, d, _, _ := geom.elements()
	// (0, 1)
	x0 := 0*a + 1*b
	y0 := 0*c + 1*d

	// (1, 0)
	x1 := 1*a + 0*b
	y1 := 1*c + 0*d

	// (1, 1)
	x2 := 1*a + 1*b
	y2 := 1*c + 1*d

	maxx := maxf32(0, x0, x1, x2)
	maxy := maxf32(0, y0, y1, y2)
	minx := minf32(0, x0, x1, x2)
	miny := minf32(0, y0, y1, y2)

	return maxx - minx, maxy - miny
}
<|endoftext|>


<commit_before>package mlmath

import (
	"errors"
	"math"
)

func check_length(p *[]float64, q *[]float64) error {
	var err error = nil

	if len(*p) != len(*q) {
		err = errors.New("mymath: Input slices have not same length")
	}

	return err
}

func Euclide(p []float64, q []float64) float64 {

	res := 0.0

	for i, val := range p {
		res += (val - q[i]) * (val - q[i])
	}
	res = math.Sqrt(res)

	return res
}

func Manhatten(p []float64, q []float64) float64 {

	res := 0.0

	for i, val := range p {
		res += math.Abs(val) - math.Abs(q[i])
	}

	return res
}
<commit_msg>fixed absolute in Manhatten<commit_after>package mlmath

import (
	"errors"
	"math"
)

func Check_length(p *[]float64, q *[]float64) error {
	var err error = nil

	if len(*p) != len(*q) {
		err = errors.New("mymath: Input slices have not same length")
	}

	return err
}

func Euclide(p []float64, q []float64) float64 {

	res := 0.0

	for i, val := range p {
		res += (val - q[i]) * (val - q[i])
	}
	res = math.Sqrt(res)

	return res
}

func Manhatten(p []float64, q []float64) float64 {

	res := 0.0

	for i, val := range p {
		res += math.Abs(val - q[i])
	}

	return res
}

func KNN() {
}
<|endoftext|>


<commit_before>package main

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

const (
	Version         = "0.1.0"
	ExitCodeSuccess = 0
	ExitCodeError   = 1
)

type Mock struct {
	Package string
	Name    string
	Screens []Screen
	Launch  Launch
	Colors  []Color
	Strings []String
}

type Screen struct {
	Id        string
	Name      string
	Layout    Layout
	Behaviors []Behavior
}

type Layout struct {
	Views []View
}

type View struct {
	Id     string
	Type   string
	Label  string
	SizeW  string `json:"size_w"`
	SizeH  string `json:"size_h"`
	AlignH string `json:"align_h"`
	AlignV string `json:"align_v"`
}

type Behavior struct {
	Trigger Trigger
	Action  Action
}

type Trigger struct {
	Type   string
	Widget string
}

type Action struct {
	Type    string
	Transit string
}

type Launch struct {
	Screen string
}

type Color struct {
	Id    string
	Value string
}

type String struct {
	Lang string
	Defs []Def
}

type Def struct {
	Id    string
	Value string
}

func main() {
	// Parse command
	if len(os.Args) == 1 {
		printUsage()
		os.Exit(ExitCodeError)
	}
	switch os.Args[1] {
	case "gen":
	case "version":
		printVersion()
		os.Exit(ExitCodeSuccess)
	case "help":
		fallthrough
	default:
		printUsage()
		os.Exit(ExitCodeError)
	}

	if len(os.Args) < 3 {
		printUsage()
		os.Exit(ExitCodeError)
	}
	genId := os.Args[2]

	mock := parseConfigs()
	gen(&mock, genId)
}

func printUsage() {
	fmt.Println(`mocker is a mock up framework for mobile apps.

Usage:
	mocker command [options]

Command:
	gen      generate source code (see 'Generator')
	help     show this help
	version  show version of mocker

Generator:
	mocker gen ID

	ID:
		ios  Objective-C code for iOS app
`)
}

func printVersion() {
	fmt.Println("mocker version \"" + Version + "\"")
}

func parseConfigs() (mock Mock) {
	filename := "Mockerfile"
	xmlFile, err := os.Open(filename)
	if err != nil {
		fmt.Println("Error opening file", err)
		return
	}
	defer xmlFile.Close()

	b, _ := ioutil.ReadAll(xmlFile)
	err = json.Unmarshal(b, &mock)
	if err != nil {
		fmt.Println("Error unmarshaling Mockerfile", err)
		return
	}

	return
}

func gen(mock *Mock, genId string) {
	switch genId {
	case "ios":
		genIos(mock)
	case "android":
		genAndroid(mock)
	default:
		fmt.Println("Invalid gen ID")
		printUsage()
		os.Exit(ExitCodeError)
	}
}

func genAndroid(mock *Mock) {
	// TODO
	fmt.Printf("%+v\n", mock)

	outDir := "out"
	srcDir := filepath.Join(outDir, "src")
	mainDir := filepath.Join(srcDir, "main")
	javaDir := filepath.Join(mainDir, "java")
	packageDir := filepath.Join(javaDir, strings.Replace(mock.Package, ".", string(os.PathSeparator), -1))
	resDir := filepath.Join(mainDir, "res")
	layoutDir := filepath.Join(resDir, "layout")
	valuesDir := filepath.Join(resDir, "values")

	// Generate base file set using android command
	cmd := exec.Command("android", "create", "project", "-n", "mock", "-v", "0.12.+", "-g", "-k", mock.Package, "-a", "DummyActivity", "-t", "android-19", "-p", outDir)
	err := cmd.Run()
	if err != nil {
		fmt.Printf("Error while generating project")
	}

	// Remove unecessery directories and files
	os.RemoveAll(filepath.Join(srcDir, "androidTest"))
	os.Remove(filepath.Join(packageDir, "DummyActivity.java"))
	os.Remove(filepath.Join(layoutDir, "main.xml"))

	// Generate Manifest
	genAndroidManifest(mock, mainDir)

	// Generate Activities
	for i := range mock.Screens {
		screen := mock.Screens[i]
		genAndroidActivity(mock, packageDir, screen)
		genAndroidActivityLayout(mock, layoutDir, screen)
	}

	// Generate resources
	genAndroidStrings(mock, valuesDir)
}

func genIos(mock *Mock) {
	// TODO
	fmt.Printf("%+v\n", mock)
}

func genAndroidManifest(mock *Mock, outDir string) {
	filename := filepath.Join(outDir, "AndroidManifest.xml")
	f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
	defer f.Close()
	f.WriteString(fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="%s" >

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
`, mock.Package))

	launcherId := mock.Launch.Screen
	for i := range mock.Screens {
		screen := mock.Screens[i]
		activityId := strings.Title(screen.Id)
		if screen.Id == launcherId {
			// Launcher
			f.WriteString(fmt.Sprintf(`        <activity
            android:label="@string/activity_title_%s"
            android:name=".%sActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
`, screen.Id, activityId))
		} else {
			f.WriteString(fmt.Sprintf(`        <activity
            android:label="@string/activity_title_%s"
            android:name=".%sActivity" />
`, screen.Id, activityId))
		}
	}

	f.WriteString(`    </application>

</manifest>
`)
	f.Close()
}

func genAndroidActivity(mock *Mock, packageDir string, screen Screen) {
	activityId := strings.Title(screen.Id)
	filename := filepath.Join(packageDir, activityId+"Activity.java")
	f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
	defer f.Close()
	f.WriteString(fmt.Sprintf(`package %s;

import android.os.Bundle;

public class %sActivity extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_%s);
		init();
	}

	private void init() {
	}

}
`, mock.Package, activityId, screen.Id))
	f.Close()
}

func genAndroidActivityLayout(mock *Mock, layoutDir string, screen Screen) {
	filename := filepath.Join(layoutDir, "activity_"+screen.Id+".xml")
	f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
	defer f.Close()
	xmlns := `xmlns:android="http://schemas.android.com/apk/res/android"`
	f.WriteString(fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
<LinearLayout %s
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="16dp" >
`, xmlns))

	for i := range screen.Layout.Views {
		view := screen.Layout.Views[i]
		switch view.Type {
		case "button":
			f.WriteString(fmt.Sprintf(`
    <Button
        android:id="@+id/%s"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/%s"
        android:textColor="#FF8800"
        android:textSize="30dp" />
`, view.Id, view.Label))
		default:
		}
	}
	f.WriteString(`</LinearLayout>
`)

	f.Close()
}

func genAndroidStrings(mock *Mock, valuesDir string) {
	filename := filepath.Join(valuesDir, "strings.xml")
	f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
	defer f.Close()
	f.WriteString(fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">%s</app_name>
`, mock.Name))

	for i := range mock.Screens {
		screen := mock.Screens[i]
		f.WriteString(fmt.Sprintf(`    <string name="activity_title_%s">%s</string>
`, screen.Id, screen.Name))
	}

	f.WriteString(`</resources>
`)
	f.Close()
}
<commit_msg>Added localized strings generator.<commit_after>package main

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

const (
	Version         = "0.1.0"
	ExitCodeSuccess = 0
	ExitCodeError   = 1
)

type Mock struct {
	Package string
	Name    string
	Screens []Screen
	Launch  Launch
	Colors  []Color
	Strings []String
}

type Screen struct {
	Id        string
	Name      string
	Layout    Layout
	Behaviors []Behavior
}

type Layout struct {
	Views []View
}

type View struct {
	Id     string
	Type   string
	Label  string
	SizeW  string `json:"size_w"`
	SizeH  string `json:"size_h"`
	AlignH string `json:"align_h"`
	AlignV string `json:"align_v"`
}

type Behavior struct {
	Trigger Trigger
	Action  Action
}

type Trigger struct {
	Type   string
	Widget string
}

type Action struct {
	Type    string
	Transit string
}

type Launch struct {
	Screen string
}

type Color struct {
	Id    string
	Value string
}

type String struct {
	Lang string
	Defs []Def
}

type Def struct {
	Id    string
	Value string
}

func main() {
	// Parse command
	if len(os.Args) == 1 {
		printUsage()
		os.Exit(ExitCodeError)
	}
	switch os.Args[1] {
	case "gen":
	case "version":
		printVersion()
		os.Exit(ExitCodeSuccess)
	case "help":
		fallthrough
	default:
		printUsage()
		os.Exit(ExitCodeError)
	}

	if len(os.Args) < 3 {
		printUsage()
		os.Exit(ExitCodeError)
	}
	genId := os.Args[2]

	mock := parseConfigs()
	gen(&mock, genId)
}

func printUsage() {
	fmt.Println(`mocker is a mock up framework for mobile apps.

Usage:
	mocker command [options]

Command:
	gen      generate source code (see 'Generator')
	help     show this help
	version  show version of mocker

Generator:
	mocker gen ID

	ID:
		ios  Objective-C code for iOS app
`)
}

func printVersion() {
	fmt.Println("mocker version \"" + Version + "\"")
}

func parseConfigs() (mock Mock) {
	filename := "Mockerfile"
	xmlFile, err := os.Open(filename)
	if err != nil {
		fmt.Println("Error opening file", err)
		return
	}
	defer xmlFile.Close()

	b, _ := ioutil.ReadAll(xmlFile)
	err = json.Unmarshal(b, &mock)
	if err != nil {
		fmt.Println("Error unmarshaling Mockerfile", err)
		return
	}

	return
}

func gen(mock *Mock, genId string) {
	switch genId {
	case "ios":
		genIos(mock)
	case "android":
		genAndroid(mock)
	default:
		fmt.Println("Invalid gen ID")
		printUsage()
		os.Exit(ExitCodeError)
	}
}

func genAndroid(mock *Mock) {
	// TODO
	fmt.Printf("%+v\n", mock)

	outDir := "out"
	srcDir := filepath.Join(outDir, "src")
	mainDir := filepath.Join(srcDir, "main")
	javaDir := filepath.Join(mainDir, "java")
	packageDir := filepath.Join(javaDir, strings.Replace(mock.Package, ".", string(os.PathSeparator), -1))
	resDir := filepath.Join(mainDir, "res")
	layoutDir := filepath.Join(resDir, "layout")
	valuesDir := filepath.Join(resDir, "values")

	// Generate base file set using android command
	cmd := exec.Command("android", "create", "project", "-n", "mock", "-v", "0.12.+", "-g", "-k", mock.Package, "-a", "DummyActivity", "-t", "android-19", "-p", outDir)
	err := cmd.Run()
	if err != nil {
		fmt.Printf("Error while generating project")
	}

	// Remove unecessery directories and files
	os.RemoveAll(filepath.Join(srcDir, "androidTest"))
	os.Remove(filepath.Join(packageDir, "DummyActivity.java"))
	os.Remove(filepath.Join(layoutDir, "main.xml"))

	// Generate Manifest
	genAndroidManifest(mock, mainDir)

	// Generate Activities
	for i := range mock.Screens {
		screen := mock.Screens[i]
		genAndroidActivity(mock, packageDir, screen)
		genAndroidActivityLayout(mock, layoutDir, screen)
	}

	// Generate resources
	genAndroidStrings(mock, valuesDir)
	genAndroidLocalizedStrings(mock, resDir)
}

func genIos(mock *Mock) {
	// TODO
	fmt.Printf("%+v\n", mock)
}

func genAndroidManifest(mock *Mock, outDir string) {
	filename := filepath.Join(outDir, "AndroidManifest.xml")
	f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
	defer f.Close()
	f.WriteString(fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="%s" >

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
`, mock.Package))

	launcherId := mock.Launch.Screen
	for i := range mock.Screens {
		screen := mock.Screens[i]
		activityId := strings.Title(screen.Id)
		if screen.Id == launcherId {
			// Launcher
			f.WriteString(fmt.Sprintf(`        <activity
            android:label="@string/activity_title_%s"
            android:name=".%sActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
`, screen.Id, activityId))
		} else {
			f.WriteString(fmt.Sprintf(`        <activity
            android:label="@string/activity_title_%s"
            android:name=".%sActivity" />
`, screen.Id, activityId))
		}
	}

	f.WriteString(`    </application>

</manifest>
`)
	f.Close()
}

func genAndroidActivity(mock *Mock, packageDir string, screen Screen) {
	activityId := strings.Title(screen.Id)
	filename := filepath.Join(packageDir, activityId+"Activity.java")
	f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
	defer f.Close()
	f.WriteString(fmt.Sprintf(`package %s;

import android.os.Bundle;

public class %sActivity extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_%s);
		init();
	}

	private void init() {
	}

}
`, mock.Package, activityId, screen.Id))
	f.Close()
}

func genAndroidActivityLayout(mock *Mock, layoutDir string, screen Screen) {
	filename := filepath.Join(layoutDir, "activity_"+screen.Id+".xml")
	f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
	defer f.Close()
	xmlns := `xmlns:android="http://schemas.android.com/apk/res/android"`
	f.WriteString(fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
<LinearLayout %s
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="16dp" >
`, xmlns))

	for i := range screen.Layout.Views {
		view := screen.Layout.Views[i]
		switch view.Type {
		case "button":
			f.WriteString(fmt.Sprintf(`
    <Button
        android:id="@+id/%s"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/%s"
        android:textColor="#FF8800"
        android:textSize="30dp" />
`, view.Id, view.Label))
		default:
		}
	}
	f.WriteString(`</LinearLayout>
`)

	f.Close()
}

func genAndroidStrings(mock *Mock, valuesDir string) {
	filename := filepath.Join(valuesDir, "strings_app.xml")
	f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
	defer f.Close()

	// App name
	f.WriteString(fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">%s</app_name>
`, mock.Name))

	// Activity title
	for i := range mock.Screens {
		screen := mock.Screens[i]
		f.WriteString(fmt.Sprintf(`    <string name="activity_title_%s">%s</string>
`, screen.Id, screen.Name))
	}

	f.WriteString(`</resources>
`)
	f.Close()
}

func genAndroidLocalizedStrings(mock *Mock, resDir string) {
	for i := range mock.Strings {
		s := mock.Strings[i]
		lang := s.Lang
		suffix := "-" + lang
		if strings.ToLower(lang) == "base" {
			suffix = ""
		}
		valuesDir := filepath.Join(resDir, "values"+suffix)
		os.MkdirAll(valuesDir, 0777)
		filename := filepath.Join(valuesDir, "strings.xml")
		f, _ := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
		defer f.Close()
		for j := range s.Defs {
			def := s.Defs[j]
			f.WriteString(fmt.Sprintf(`    <string name="%s">%s</string>
`, def.Id, def.Value))
		}
		f.Close()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"time"

	"gopkg.in/mgo.v2/bson"
)

type Indicator struct {
	ID   bson.ObjectId `bson:"_id" json:"id"`
	Name string        `bson:"name" json:"name"`
	Key  string        `bson:"key" json:"key"`
}

type Repository struct {
	ID         int    `json:"id"`
	Owner      string `json:"owner"`
	OwnerIsOrg bool   `json:"owner_is_org"`
	Name       string `json:"name"`
	FullName   string `json:"full_name"`
	URL        string `json:"url"`
}

type Docs struct {
	Repository    Repository `json:"repository"`
	ReadmeExists  bool       `json:"readme_exists"`
	ReadmeURL     string     `json:"readme_url"`
	LicenseExists bool       `json:"license_exists"`
	LicenseURL    string     `json:"license_url"`
	LicenseName   string     `json:"license_name"`
}

type ResponseTimes struct {
	Repository           Repository            `json:"repository"`
	AverageResponseTimes []AverageResponseTime `json:"average_response_times"`
}

type AverageResponseTime struct {
	Contributor           string    `json:"contributor"`
	AverageResponseTime   float32   `json:"average_response_time"`
	FirstContributionWeek time.Time `json:"first_contribution_week"`
}

type PullRequests struct {
	Repository         Repository `json:"repository"`
	TotalPullRequests  string     `json:"total_pull_requests"`
	MergedPullRequests string     `json:"merged_pull_requests"`
	SentVsMerged       Frequency  `json:"sent_vs_merged"`
}

type Issues struct {
	Repository         Repository `json:"repository"`
	TotalIssues        string     `json:"total_issues"`
	AverageTimeToClose string     `json:"average_time_to_close"`
	OpenVsClosed       Frequency  `json:"open_vs_closed"`
}

type Commits struct {
	Repository    Repository `json:"repository"`
	TotalCommits  int        `json:"total_commits"`
	FirstCommitAt time.Time  `json:"first_commit_at"`
	LastCommitAt  time.Time  `json:"last_commit_at"`
	CodeFrequency Frequency  `json:"code_frequency"`
}

type Frequency struct {
	PerWeek  string `json:"per_week"`
	PerMonth string `json:"per_month"`
}
<commit_msg>Update models<commit_after>package main

import (
	"time"

	"gopkg.in/mgo.v2/bson"
)

type Indicator struct {
	ID   bson.ObjectId `bson:"_id" json:"id"`
	Name string        `bson:"name" json:"name"`
	Key  string        `bson:"key" json:"key"`
}

type Repository struct {
	ID         int    `json:"id"`
	Owner      string `json:"owner"`
	OwnerIsOrg bool   `json:"owner_is_org"`
	Name       string `json:"name"`
	FullName   string `json:"full_name"`
	URL        string `json:"url"`
}

type Docs struct {
	Repository    Repository `json:"repository"`
	ReadmeExists  bool       `json:"readme_exists"`
	ReadmeURL     string     `json:"readme_url"`
	LicenseExists bool       `json:"license_exists"`
	LicenseURL    string     `json:"license_url"`
	LicenseName   string     `json:"license_name"`
}

type ResponseTimes struct {
	Repository           Repository            `json:"repository"`
	AverageResponseTimes []AverageResponseTime `json:"average_response_times"`
}

type AverageResponseTime struct {
	Contributor           string    `json:"contributor"`
	AverageResponseTime   float32   `json:"average_response_time"`
	FirstContributionWeek time.Time `json:"first_contribution_week"`
}

type Commits struct {
	Repository      Repository `json:"repository"`
	TotalCommits    uint8      `json:"total_commits"`
	FirstCommitAt   time.Time  `json:"first_commit_at"`
	FirstCommitBy   string     `json:"first_commit_by"`
	LastCommitAt    time.Time  `json:"last_commit_at"`
	LastCommitBy    string     `json:"last_commit_by"`
	CommitFrequency []PerWeek  `json:"commit_frequency"`
}

type PullRequests struct {
	Repository         Repository `json:"repository"`
	TotalPullRequests  string     `json:"total_pull_requests"`
	MergedPullRequests string     `json:"merged_pull_requests"`
	SentVsMerged       []PerWeek  `json:"sent_vs_merged"`
}

type Issues struct {
	Repository         Repository `json:"repository"`
	TotalIssues        string     `json:"total_issues"`
	AverageTimeToClose string     `json:"average_time_to_close"`
	OpenVsClosed       []PerWeek  `json:"open_vs_closed"`
}

type PerWeek struct {
	Week   uint  `json:"week"`
	Number uint8 `json:"number"`
}
<|endoftext|>


<commit_before><commit_msg>applyconfiguration-gen handling of types that have a non-embedded use of TypeMeta<commit_after><|endoftext|>


<commit_before>/*
    MP3Cat is a fast command line utility for concatenating MP3 files
    without re-encoding. It supports both constant bit rate (CBR) and
    variable bit rate (VBR) files.
*/
package main

import (
    "fmt"
    "io"
    "os"
    "path/filepath"
    "github.com/dmulholland/mp3lib"
    "github.com/dmulholland/clio/go/clio"
)

// Application version number.
const version = "2.2.1"

// Command line help text.
var helptext = fmt.Sprintf(`
Usage: %s [FLAGS] [OPTIONS] ARGUMENTS

  Concatenates MP3 files without re-encoding. Supports both constant bit rate
  (CBR) and variable bit rate (VBR) files. Strips ID3 tags and garbage data
  from the output.

Arguments:
  <files>           List of input files to merge.

Options:
  -o, --out <file>  Output filename. Defaults to 'output.mp3'.

Flags:
  -f, --force       Overwrite an existing output file.
      --help        Display this help text and exit.
  -v, --verbose     Report progress.
      --version     Display the application's version number and exit.
`, filepath.Base(os.Args[0]))

// Application entry point.
func main() {

    // Initialize an argument parser.
    parser := clio.NewParser(helptext, version)

    // Register flags.
    parser.AddFlag("force f")
    parser.AddFlag("verbose v")
    parser.AddFlag("debug d")

    // Register options.
    parser.AddStr("out o", "output.mp3")

    // Parse the command line arguments.
    parser.Parse()

    // Make sure we have a list of input files.
    if !parser.HasArgs() {
        fmt.Fprintln(
            os.Stderr,
            "Error: you must supply a list of files to merge.")
        os.Exit(1)
    }

    // Set debug mode if the user supplied a --debug flag.
    if parser.GetFlag("debug") {
        mp3lib.DebugMode = true
    }

    // Merge the input files.
    mergeFiles(
        parser.GetStr("out"),
        parser.GetArgs(),
        parser.GetFlag("force"),
        parser.GetFlag("verbose"))
}

// Create a new file at the specified output path containing the merged
// contents of the list of input files.
func mergeFiles(outputPath string, inputPaths []string, force, verbose bool) {

    var totalFrames uint32
    var totalBytes uint32
    var totalFiles int
    var firstBitRate int
    var isVBR bool

    // Only overwrite an existing file if the --force flag has been used.
    if _, err := os.Stat(outputPath); err == nil {
        if !force {
            fmt.Fprintf(
                os.Stderr,
                "Error: the file '%v' already exists.\n", outputPath)
            os.Exit(1)
        }
    }

    // If the list of input files includes the output file we'll end up in an
    // infinite loop.
    for _, filepath := range inputPaths {
        if filepath == outputPath {
            fmt.Fprintln(
                os.Stderr,
                "Error: the list of input files includes the output file.")
            os.Exit(1)
        }
    }

    // Create the output file.
    outputFile, err := os.Create(outputPath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    // Loop over the input files and append their MP3 frames to the output file.
    for _, filepath := range inputPaths {

        if verbose {
            fmt.Println("Merging:", filepath)
        }

        inputFile, err := os.Open(filepath)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        isFirstFrame := true

        for {

            // Read the next frame from the input file.
            frame := mp3lib.NextFrame(inputFile)
            if frame == nil {
                break
            }

            // Skip the first frame if it's a VBR header.
            if isFirstFrame {
                isFirstFrame = false
                if mp3lib.IsXingHeader(frame) || mp3lib.IsVbriHeader(frame) {
                    continue
                }
            }

            // If we detect more than one bitrate we'll need to add a VBR
            // header to the output file.
            if firstBitRate == 0 {
                firstBitRate = frame.BitRate
            } else if frame.BitRate != firstBitRate {
                isVBR = true
            }

            // Write the frame to the output file.
            _, err := outputFile.Write(frame.RawBytes)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }

            totalFrames += 1
            totalBytes += uint32(len(frame.RawBytes))
        }

        inputFile.Close()
        totalFiles += 1
    }

    outputFile.Close()

    // If we detected multiple bitrates, prepend a VBR header to the file.
    if isVBR {
        if verbose {
            fmt.Println("VBR data detected. Adding Xing header.")
        }
        addXingHeader(outputPath, totalFrames, totalBytes)
    }

    if verbose {
        fmt.Printf("%v files merged.\n", totalFiles)
    }
}

// Prepend an Xing VBR header to the specified MP3 file.
func addXingHeader(filepath string, totalFrames, totalBytes uint32) {

    outputFile, err := os.Create(filepath + ".mp3cat.tmp")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    inputFile, err := os.Open(filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    templateFrame := mp3lib.NextFrame(inputFile)
    inputFile.Seek(0, 0)

    xingHeader := mp3lib.NewXingHeader(templateFrame, totalFrames, totalBytes)

    _, err = outputFile.Write(xingHeader.RawBytes)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    _, err = io.Copy(outputFile, inputFile)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    outputFile.Close()
    inputFile.Close()

    err = os.Remove(filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    err = os.Rename(filepath + ".mp3cat.tmp", filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}
<commit_msg>Housekeeping<commit_after>/*
    MP3Cat is a fast command line utility for concatenating MP3 files
    without re-encoding. It supports both constant bit rate (CBR) and
    variable bit rate (VBR) files.
*/
package main

import (
    "fmt"
    "io"
    "os"
    "path/filepath"
    "github.com/dmulholland/mp3lib"
    "github.com/dmulholland/clio/go/clio"
)

// Application version number.
const version = "2.2.1"

// Command line help text.
var helptext = fmt.Sprintf(`
Usage: %s [FLAGS] [OPTIONS] ARGUMENTS

  Concatenates MP3 files without re-encoding. Supports both constant bit rate
  (CBR) and variable bit rate (VBR) files. Strips ID3 tags and garbage data
  from the output.

Arguments:
  <files>           List of input files to merge.

Options:
  -o, --out <file>  Output filename. Defaults to 'output.mp3'.

Flags:
  -f, --force       Overwrite an existing output file.
      --help        Display this help text and exit.
  -v, --verbose     Report progress.
      --version     Display the application's version number and exit.
`, filepath.Base(os.Args[0]))

// Application entry point.
func main() {

    // Initialize an argument parser.
    parser := clio.NewParser(helptext, version)

    // Register flags.
    parser.AddFlag("force f")
    parser.AddFlag("verbose v")
    parser.AddFlag("debug d")

    // Register options.
    parser.AddStr("out o", "output.mp3")

    // Parse the command line arguments.
    parser.Parse()

    // Make sure we have a list of input files.
    if !parser.HasArgs() {
        fmt.Fprintln(
            os.Stderr,
            "Error: you must supply a list of files to merge.")
        os.Exit(1)
    }

    // Set debug mode if the user supplied a --debug flag.
    if parser.GetFlag("debug") {
        mp3lib.DebugMode = true
    }

    // Merge the input files.
    mergeFiles(
        parser.GetStr("out"),
        parser.GetArgs(),
        parser.GetFlag("force"),
        parser.GetFlag("verbose"))
}

// Create a new file at the specified output path containing the merged
// contents of the list of input files.
func mergeFiles(outputPath string, inputPaths []string, force, verbose bool) {

    var totalFrames uint32
    var totalBytes uint32
    var totalFiles int
    var firstBitRate int
    var isVBR bool

    // Only overwrite an existing file if the --force flag has been used.
    if _, err := os.Stat(outputPath); err == nil {
        if !force {
            fmt.Fprintf(
                os.Stderr,
                "Error: the file '%v' already exists.\n", outputPath)
            os.Exit(1)
        }
    }

    // If the list of input files includes the output file we'll end up in an
    // infinite loop.
    for _, filepath := range inputPaths {
        if filepath == outputPath {
            fmt.Fprintln(
                os.Stderr,
                "Error: the list of input files includes the output file.")
            os.Exit(1)
        }
    }

    // Create the output file.
    outputFile, err := os.Create(outputPath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    // Loop over the input files and append their MP3 frames to the output
    // file.
    for _, filepath := range inputPaths {

        if verbose {
            fmt.Println("Merging:", filepath)
        }

        inputFile, err := os.Open(filepath)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        isFirstFrame := true

        for {

            // Read the next frame from the input file.
            frame := mp3lib.NextFrame(inputFile)
            if frame == nil {
                break
            }

            // Skip the first frame if it's a VBR header.
            if isFirstFrame {
                isFirstFrame = false
                if mp3lib.IsXingHeader(frame) || mp3lib.IsVbriHeader(frame) {
                    continue
                }
            }

            // If we detect more than one bitrate we'll need to add a VBR
            // header to the output file.
            if firstBitRate == 0 {
                firstBitRate = frame.BitRate
            } else if frame.BitRate != firstBitRate {
                isVBR = true
            }

            // Write the frame to the output file.
            _, err := outputFile.Write(frame.RawBytes)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }

            totalFrames += 1
            totalBytes += uint32(len(frame.RawBytes))
        }

        inputFile.Close()
        totalFiles += 1
    }

    outputFile.Close()

    // If we detected multiple bitrates, prepend a VBR header to the file.
    if isVBR {
        if verbose {
            fmt.Println("VBR data detected. Adding Xing header.")
        }
        addXingHeader(outputPath, totalFrames, totalBytes)
    }

    if verbose {
        fmt.Printf("%v files merged.\n", totalFiles)
    }
}

// Prepend an Xing VBR header to the specified MP3 file.
func addXingHeader(filepath string, totalFrames, totalBytes uint32) {

    outputFile, err := os.Create(filepath + ".mp3cat.tmp")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    inputFile, err := os.Open(filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    templateFrame := mp3lib.NextFrame(inputFile)
    inputFile.Seek(0, 0)

    xingHeader := mp3lib.NewXingHeader(templateFrame, totalFrames, totalBytes)

    _, err = outputFile.Write(xingHeader.RawBytes)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    _, err = io.Copy(outputFile, inputFile)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    outputFile.Close()
    inputFile.Close()

    err = os.Remove(filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    err = os.Rename(filepath + ".mp3cat.tmp", filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}
<|endoftext|>