<commit_before>// Package templates centralizes access to template functions that render the
// power-league content as HTML.
package templates

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"html/template"
	"io"
	"strconv"
	"strings"

	"github.com/Forestmb/goff"
	"github.com/Forestmb/power-league/rankings"
	"github.com/golang/glog"
)

const (
	// DefaultBaseDir is the default location of the directory containing the
	// template files.
	DefaultBaseDir = "html/"

	baseTemplate     = "base.html"
	aboutTemplate    = "about.html"
	errorTemplate    = "error.html"
	leaguesTemplate  = "leagues.html"
	rankingsTemplate = "rankings.html"
)

// Templates provides programmtic access to power rankings templates
type Templates interface {
	WriteAboutTemplate(w io.Writer, content *AboutPageContent) error
	WriteErrorTemplate(w io.Writer, content *ErrorPageContent) error
	WriteLeaguesTemplate(w io.Writer, content *LeaguesPageContent) error
	WriteRankingsTemplate(w io.Writer, content *RankingsPageContent) error
}

// defaultTemplates provides programmtic access to power rankings templates
// created from files within a single base directory
type defaultTemplates struct {
	// Directory containing the HTML template files
	baseDir string
}

// NewTemplates creates new templates using the `DefaultBaseDir`
func NewTemplates() Templates {
	return NewTemplatesFromDir(DefaultBaseDir)
}

// NewTemplatesFromDir creates new templates using the given directory as the base
func NewTemplatesFromDir(dir string) Templates {
	glog.V(2).Infof("creating new templates -- dir=%s", dir)
	return &defaultTemplates{
		baseDir: dir,
	}
}

//
// Template Data Structures
//

// RankingsPageContent is used to show an given league's power rankings up
// to a certain week in the season.
type RankingsPageContent struct {
	Weeks           int
	League          *goff.League
	LeagueStarted   bool
	LeaguePowerData *rankings.LeaguePowerData
	LoggedIn        bool
	SiteConfig      *SiteConfig
}

// YearlyLeagues describes the leagues for a user for a given year.
type YearlyLeagues struct {
	Year    string
	Leagues []goff.League
}

// AllYearlyLeagues contains leagues for multiple years.
type AllYearlyLeagues []*YearlyLeagues

// LeaguesPageContent describes the leagues a user has participated in across
// multiple years.
type LeaguesPageContent struct {
	AllYears   AllYearlyLeagues
	LoggedIn   bool
	SiteConfig *SiteConfig
}

// AboutPageContent describes the power rankings web site.
type AboutPageContent struct {
	LoggedIn   bool
	SiteConfig *SiteConfig
}

// ErrorPageContent describes an error that has occurred in the application.
type ErrorPageContent struct {
	Message    string
	LoggedIn   bool
	SiteConfig *SiteConfig
}

// SiteConfig provides configuration info about the site that can be used on
// all pages.
type SiteConfig struct {
	BaseContext         string
	StaticContext       string
	AnalyticsTrackingID string
}

// SiteLink represents a link to a page in the site's navigation.
type SiteLink struct {
	Link string
	Name string
}

// WriteRankingsTemplate writes the raknings template to the given writer
func (t *defaultTemplates) WriteRankingsTemplate(w io.Writer, content *RankingsPageContent) error {
	funcMap := template.FuncMap{
		"getPowerScore":          templateGetPowerScore,
		"getRecord":              templateGetRecord,
		"getRankings":            templateGetRankings,
		"getActualRank":          templateGetActualRank,
		"getPlacingTeams":        templateGetPlacingTeams,
		"getPlaceFromRank":       templateGetPlaceFromRank,
		"getTeamPosition":        templateGetTeamPosition,
		"getRankOffset":          templateGetRankOffset,
		"getRankForPreviousWeek": templateGetRankForPreviousWeek,
		"getCSVContent":          templateGetCSVContent,
		"getExportFilename":      templateGetExportFilename,
	}
	template, err := template.New(rankingsTemplate).Funcs(funcMap).ParseFiles(
		t.baseDir+baseTemplate,
		t.baseDir+rankingsTemplate)
	if err != nil {
		return err
	}
	return template.Execute(w, content)
}

// WriteAboutTemplate writes the about page template to the given writer
func (t *defaultTemplates) WriteAboutTemplate(w io.Writer, content *AboutPageContent) error {
	template, err := template.New(aboutTemplate).ParseFiles(
		t.baseDir+baseTemplate,
		t.baseDir+aboutTemplate)
	if err != nil {
		return err
	}
	return template.Execute(w, content)
}

// WriteLeaguesTemplate writes the leagues page template to the given writer
func (t *defaultTemplates) WriteLeaguesTemplate(w io.Writer, content *LeaguesPageContent) error {

	funcMap := template.FuncMap{
		"getTitleFromYear": templateGetTitleFromYear,
	}
	template, err := template.New(leaguesTemplate).Funcs(funcMap).ParseFiles(
		t.baseDir+baseTemplate,
		t.baseDir+leaguesTemplate)
	if err != nil {
		return err
	}

	return template.Execute(w, content)
}

// WriteErrorTemplate writes the error page template to the given writer
func (t *defaultTemplates) WriteErrorTemplate(w io.Writer, content *ErrorPageContent) error {
	template, err := template.New(errorTemplate).ParseFiles(
		t.baseDir+baseTemplate,
		t.baseDir+errorTemplate)
	if err != nil {
		return err
	}
	return template.Execute(w, content)
}

//
// Template functions
//

func templateGetTitleFromYear(year string) string {
	if year == "nfl" || year == "" {
		return "Current Leagues"
	}
	return year
}

func templateGetActualRank(teamData *rankings.TeamPowerData) int {
	if teamData.HasProjections {
		return teamData.ProjectedRank
	}
	return teamData.Rank
}

func templateGetPlaceFromRank(rank int, places ...string) string {
	return places[rank-1]
}

func templateGetPlacingTeams(powerData []*rankings.TeamPowerData) []*rankings.TeamPowerData {
	var placingTeams []*rankings.TeamPowerData
	for _, teamData := range powerData {
		if templateGetActualRank(teamData) <= 3 {
			placingTeams = append(placingTeams, teamData)
		}
	}
	return placingTeams
}

func templateGetRecord(week int, teamPowerData *rankings.TeamPowerData) *goff.Record {
	return teamPowerData.AllRankings[week-1].OverallRecord
}

func templateGetPowerScore(week int, teamPowerData *rankings.TeamPowerData) string {
	return fmt.Sprintf("%.0f", teamPowerData.AllRankings[week-1].PowerScore)
}

func templateGetRankings(powerData rankings.LeaguePowerData, finished bool) []*rankings.TeamPowerData {
	if finished {
		return powerData.OverallRankings
	}
	return powerData.ProjectedRankings
}

func templateGetTeamPosition(teamID uint64, rankings []*rankings.TeamPowerData) int {
	for index, scoreData := range rankings {
		if scoreData.Team.TeamID == teamID {
			return index + 1
		}
	}
	return -1
}

func templateGetRankOffset(powerRank, leagueRank int) string {
	offset := powerRank - leagueRank
	return fmt.Sprintf("%+d", offset)
}

func templateGetRankForPreviousWeek(teamPowerData *rankings.TeamPowerData, currentWeek int) string {
	if currentWeek > 1 {
		previousWeekIndex := (currentWeek - 1) - 1
		previousWeekRank := teamPowerData.AllRankings[previousWeekIndex].Rank
		return fmt.Sprintf("%d", previousWeekRank)
	}
	return ""
}

// templateGetExportFilename creates a filename for a file containing the
// power rankings data for that league
func templateGetExportFilename(l *goff.League) string {
	return fmt.Sprintf("power-rankings-%s",
		strings.Replace(
			strings.ToLower(l.Name),
			" ",
			"-",
			-1))
}

func templateGetCSVContent(leagueData *rankings.LeaguePowerData) string {
	var buffer bytes.Buffer
	separator := ","
	buffer.WriteString("Rank,")
	buffer.WriteString("Projected Rank,")
	buffer.WriteString("Team,")
	buffer.WriteString("Manager,")
	buffer.WriteString("Power Points,")
	buffer.WriteString("Projected Power Points,")
	buffer.WriteString("All-Play Record,")
	buffer.WriteString("Projected All-Play Record,")
	buffer.WriteString("League Rank,")
	buffer.WriteString("League Rank Offset,")
	buffer.WriteString("League Record")
	for index, weeklyRanking := range leagueData.ByWeek {
		var weekStr string
		if weeklyRanking.Projected {
			weekStr = fmt.Sprintf(",[Projected] Week %d ", index+1)
		} else {
			weekStr = fmt.Sprintf(",Week %d ", index+1)
		}
		buffer.WriteString(weekStr)
		buffer.WriteString("Fantasy Points")
		buffer.WriteString(weekStr)
		buffer.WriteString("Weekly Rank")
		buffer.WriteString(weekStr)
		buffer.WriteString("Overall Rank")
		buffer.WriteString(weekStr)
		buffer.WriteString("Overall Power Points")
		buffer.WriteString(weekStr)
		buffer.WriteString("Overall All-Play Record")
	}
	buffer.WriteString("\n")
	for _, teamData := range leagueData.OverallRankings {
		buffer.WriteString(strconv.Itoa(teamData.Rank))
		buffer.WriteString(separator)
		buffer.WriteString(strconv.Itoa(teamData.ProjectedRank))
		buffer.WriteString(separator)
		buffer.WriteString(teamData.Team.Name)
		buffer.WriteString(separator)
		buffer.WriteString(teamData.Team.Managers[0].Nickname)
		buffer.WriteString(separator)
		buffer.WriteString(
			strconv.FormatFloat(teamData.TotalPowerScore, 'f', 2, 64))
		buffer.WriteString(separator)
		buffer.WriteString(
			strconv.FormatFloat(teamData.ProjectedPowerScore, 'f', 2, 64))
		buffer.WriteString(separator)
		writeRecordToBuffer(&buffer, teamData.OverallRecord)
		buffer.WriteString(separator)
		writeRecordToBuffer(&buffer, teamData.OverallProjectedRecord)
		buffer.WriteString(separator)
		buffer.WriteString(strconv.Itoa(teamData.Team.TeamStandings.Rank))
		buffer.WriteString(separator)
		buffer.WriteString(
			templateGetRankOffset(
				teamData.Rank,
				teamData.Team.TeamStandings.Rank))
		buffer.WriteString(separator)
		writeRecordToBuffer(&buffer, &teamData.Team.TeamStandings.Record)
		for index, weeklyScore := range teamData.AllScores {
			buffer.WriteString(separator)
			buffer.WriteString(strconv.FormatFloat(weeklyScore.Score, 'f', 2, 64))
			buffer.WriteString(separator)
			buffer.WriteString(strconv.Itoa(weeklyScore.Rank))
			buffer.WriteString(separator)
			buffer.WriteString(strconv.Itoa(teamData.AllRankings[index].Rank))
			buffer.WriteString(separator)
			buffer.WriteString(
				strconv.FormatFloat(
					teamData.AllRankings[index].PowerScore, 'f', 2, 64))
			buffer.WriteString(separator)
			writeRecordToBuffer(
				&buffer, teamData.AllRankings[index].OverallRecord)
		}
		buffer.WriteString("\n")
	}

	return base64.StdEncoding.EncodeToString(buffer.Bytes())
}

func writeRecordToBuffer(buffer *bytes.Buffer, r *goff.Record) {
	buffer.WriteString(strconv.Itoa(r.Wins))
	buffer.WriteString("-")
	buffer.WriteString(strconv.Itoa(r.Losses))
	buffer.WriteString("-")
	buffer.WriteString(strconv.Itoa(r.Ties))
}

//
// Sorting
//

func (y AllYearlyLeagues) Len() int {
	return len(y)
}

func (y AllYearlyLeagues) Less(i, j int) bool {
	firstYear, _ := strconv.Atoi(y[i].Year)
	secondYear, _ := strconv.Atoi(y[j].Year)
	return firstYear > secondYear
}

func (y AllYearlyLeagues) Swap(i, j int) {
	y[i], y[j] = y[j], y[i]
}
<commit_msg>Removing unused type 'SiteLink'<commit_after>// Package templates centralizes access to template functions that render the
// power-league content as HTML.
package templates

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"html/template"
	"io"
	"strconv"
	"strings"

	"github.com/Forestmb/goff"
	"github.com/Forestmb/power-league/rankings"
	"github.com/golang/glog"
)

const (
	// DefaultBaseDir is the default location of the directory containing the
	// template files.
	DefaultBaseDir = "html/"

	baseTemplate     = "base.html"
	aboutTemplate    = "about.html"
	errorTemplate    = "error.html"
	leaguesTemplate  = "leagues.html"
	rankingsTemplate = "rankings.html"
)

// Templates provides programmtic access to power rankings templates
type Templates interface {
	WriteAboutTemplate(w io.Writer, content *AboutPageContent) error
	WriteErrorTemplate(w io.Writer, content *ErrorPageContent) error
	WriteLeaguesTemplate(w io.Writer, content *LeaguesPageContent) error
	WriteRankingsTemplate(w io.Writer, content *RankingsPageContent) error
}

// defaultTemplates provides programmtic access to power rankings templates
// created from files within a single base directory
type defaultTemplates struct {
	// Directory containing the HTML template files
	baseDir string
}

// NewTemplates creates new templates using the `DefaultBaseDir`
func NewTemplates() Templates {
	return NewTemplatesFromDir(DefaultBaseDir)
}

// NewTemplatesFromDir creates new templates using the given directory as the base
func NewTemplatesFromDir(dir string) Templates {
	glog.V(2).Infof("creating new templates -- dir=%s", dir)
	return &defaultTemplates{
		baseDir: dir,
	}
}

//
// Template Data Structures
//

// RankingsPageContent is used to show an given league's power rankings up
// to a certain week in the season.
type RankingsPageContent struct {
	Weeks           int
	League          *goff.League
	LeagueStarted   bool
	LeaguePowerData *rankings.LeaguePowerData
	LoggedIn        bool
	SiteConfig      *SiteConfig
}

// YearlyLeagues describes the leagues for a user for a given year.
type YearlyLeagues struct {
	Year    string
	Leagues []goff.League
}

// AllYearlyLeagues contains leagues for multiple years.
type AllYearlyLeagues []*YearlyLeagues

// LeaguesPageContent describes the leagues a user has participated in across
// multiple years.
type LeaguesPageContent struct {
	AllYears   AllYearlyLeagues
	LoggedIn   bool
	SiteConfig *SiteConfig
}

// AboutPageContent describes the power rankings web site.
type AboutPageContent struct {
	LoggedIn   bool
	SiteConfig *SiteConfig
}

// ErrorPageContent describes an error that has occurred in the application.
type ErrorPageContent struct {
	Message    string
	LoggedIn   bool
	SiteConfig *SiteConfig
}

// SiteConfig provides configuration info about the site that can be used on
// all pages.
type SiteConfig struct {
	BaseContext         string
	StaticContext       string
	AnalyticsTrackingID string
}

// WriteRankingsTemplate writes the raknings template to the given writer
func (t *defaultTemplates) WriteRankingsTemplate(w io.Writer, content *RankingsPageContent) error {
	funcMap := template.FuncMap{
		"getPowerScore":          templateGetPowerScore,
		"getRecord":              templateGetRecord,
		"getRankings":            templateGetRankings,
		"getActualRank":          templateGetActualRank,
		"getPlacingTeams":        templateGetPlacingTeams,
		"getPlaceFromRank":       templateGetPlaceFromRank,
		"getTeamPosition":        templateGetTeamPosition,
		"getRankOffset":          templateGetRankOffset,
		"getRankForPreviousWeek": templateGetRankForPreviousWeek,
		"getCSVContent":          templateGetCSVContent,
		"getExportFilename":      templateGetExportFilename,
	}
	template, err := template.New(rankingsTemplate).Funcs(funcMap).ParseFiles(
		t.baseDir+baseTemplate,
		t.baseDir+rankingsTemplate)
	if err != nil {
		return err
	}
	return template.Execute(w, content)
}

// WriteAboutTemplate writes the about page template to the given writer
func (t *defaultTemplates) WriteAboutTemplate(w io.Writer, content *AboutPageContent) error {
	template, err := template.New(aboutTemplate).ParseFiles(
		t.baseDir+baseTemplate,
		t.baseDir+aboutTemplate)
	if err != nil {
		return err
	}
	return template.Execute(w, content)
}

// WriteLeaguesTemplate writes the leagues page template to the given writer
func (t *defaultTemplates) WriteLeaguesTemplate(w io.Writer, content *LeaguesPageContent) error {

	funcMap := template.FuncMap{
		"getTitleFromYear": templateGetTitleFromYear,
	}
	template, err := template.New(leaguesTemplate).Funcs(funcMap).ParseFiles(
		t.baseDir+baseTemplate,
		t.baseDir+leaguesTemplate)
	if err != nil {
		return err
	}

	return template.Execute(w, content)
}

// WriteErrorTemplate writes the error page template to the given writer
func (t *defaultTemplates) WriteErrorTemplate(w io.Writer, content *ErrorPageContent) error {
	template, err := template.New(errorTemplate).ParseFiles(
		t.baseDir+baseTemplate,
		t.baseDir+errorTemplate)
	if err != nil {
		return err
	}
	return template.Execute(w, content)
}

//
// Template functions
//

func templateGetTitleFromYear(year string) string {
	if year == "nfl" || year == "" {
		return "Current Leagues"
	}
	return year
}

func templateGetActualRank(teamData *rankings.TeamPowerData) int {
	if teamData.HasProjections {
		return teamData.ProjectedRank
	}
	return teamData.Rank
}

func templateGetPlaceFromRank(rank int, places ...string) string {
	return places[rank-1]
}

func templateGetPlacingTeams(powerData []*rankings.TeamPowerData) []*rankings.TeamPowerData {
	var placingTeams []*rankings.TeamPowerData
	for _, teamData := range powerData {
		if templateGetActualRank(teamData) <= 3 {
			placingTeams = append(placingTeams, teamData)
		}
	}
	return placingTeams
}

func templateGetRecord(week int, teamPowerData *rankings.TeamPowerData) *goff.Record {
	return teamPowerData.AllRankings[week-1].OverallRecord
}

func templateGetPowerScore(week int, teamPowerData *rankings.TeamPowerData) string {
	return fmt.Sprintf("%.0f", teamPowerData.AllRankings[week-1].PowerScore)
}

func templateGetRankings(powerData rankings.LeaguePowerData, finished bool) []*rankings.TeamPowerData {
	if finished {
		return powerData.OverallRankings
	}
	return powerData.ProjectedRankings
}

func templateGetTeamPosition(teamID uint64, rankings []*rankings.TeamPowerData) int {
	for index, scoreData := range rankings {
		if scoreData.Team.TeamID == teamID {
			return index + 1
		}
	}
	return -1
}

func templateGetRankOffset(powerRank, leagueRank int) string {
	offset := powerRank - leagueRank
	return fmt.Sprintf("%+d", offset)
}

func templateGetRankForPreviousWeek(teamPowerData *rankings.TeamPowerData, currentWeek int) string {
	if currentWeek > 1 {
		previousWeekIndex := (currentWeek - 1) - 1
		previousWeekRank := teamPowerData.AllRankings[previousWeekIndex].Rank
		return fmt.Sprintf("%d", previousWeekRank)
	}
	return ""
}

// templateGetExportFilename creates a filename for a file containing the
// power rankings data for that league
func templateGetExportFilename(l *goff.League) string {
	return fmt.Sprintf("power-rankings-%s",
		strings.Replace(
			strings.ToLower(l.Name),
			" ",
			"-",
			-1))
}

func templateGetCSVContent(leagueData *rankings.LeaguePowerData) string {
	var buffer bytes.Buffer
	separator := ","
	buffer.WriteString("Rank,")
	buffer.WriteString("Projected Rank,")
	buffer.WriteString("Team,")
	buffer.WriteString("Manager,")
	buffer.WriteString("Power Points,")
	buffer.WriteString("Projected Power Points,")
	buffer.WriteString("All-Play Record,")
	buffer.WriteString("Projected All-Play Record,")
	buffer.WriteString("League Rank,")
	buffer.WriteString("League Rank Offset,")
	buffer.WriteString("League Record")
	for index, weeklyRanking := range leagueData.ByWeek {
		var weekStr string
		if weeklyRanking.Projected {
			weekStr = fmt.Sprintf(",[Projected] Week %d ", index+1)
		} else {
			weekStr = fmt.Sprintf(",Week %d ", index+1)
		}
		buffer.WriteString(weekStr)
		buffer.WriteString("Fantasy Points")
		buffer.WriteString(weekStr)
		buffer.WriteString("Weekly Rank")
		buffer.WriteString(weekStr)
		buffer.WriteString("Overall Rank")
		buffer.WriteString(weekStr)
		buffer.WriteString("Overall Power Points")
		buffer.WriteString(weekStr)
		buffer.WriteString("Overall All-Play Record")
	}
	buffer.WriteString("\n")
	for _, teamData := range leagueData.OverallRankings {
		buffer.WriteString(strconv.Itoa(teamData.Rank))
		buffer.WriteString(separator)
		buffer.WriteString(strconv.Itoa(teamData.ProjectedRank))
		buffer.WriteString(separator)
		buffer.WriteString(teamData.Team.Name)
		buffer.WriteString(separator)
		buffer.WriteString(teamData.Team.Managers[0].Nickname)
		buffer.WriteString(separator)
		buffer.WriteString(
			strconv.FormatFloat(teamData.TotalPowerScore, 'f', 2, 64))
		buffer.WriteString(separator)
		buffer.WriteString(
			strconv.FormatFloat(teamData.ProjectedPowerScore, 'f', 2, 64))
		buffer.WriteString(separator)
		writeRecordToBuffer(&buffer, teamData.OverallRecord)
		buffer.WriteString(separator)
		writeRecordToBuffer(&buffer, teamData.OverallProjectedRecord)
		buffer.WriteString(separator)
		buffer.WriteString(strconv.Itoa(teamData.Team.TeamStandings.Rank))
		buffer.WriteString(separator)
		buffer.WriteString(
			templateGetRankOffset(
				teamData.Rank,
				teamData.Team.TeamStandings.Rank))
		buffer.WriteString(separator)
		writeRecordToBuffer(&buffer, &teamData.Team.TeamStandings.Record)
		for index, weeklyScore := range teamData.AllScores {
			buffer.WriteString(separator)
			buffer.WriteString(strconv.FormatFloat(weeklyScore.Score, 'f', 2, 64))
			buffer.WriteString(separator)
			buffer.WriteString(strconv.Itoa(weeklyScore.Rank))
			buffer.WriteString(separator)
			buffer.WriteString(strconv.Itoa(teamData.AllRankings[index].Rank))
			buffer.WriteString(separator)
			buffer.WriteString(
				strconv.FormatFloat(
					teamData.AllRankings[index].PowerScore, 'f', 2, 64))
			buffer.WriteString(separator)
			writeRecordToBuffer(
				&buffer, teamData.AllRankings[index].OverallRecord)
		}
		buffer.WriteString("\n")
	}

	return base64.StdEncoding.EncodeToString(buffer.Bytes())
}

func writeRecordToBuffer(buffer *bytes.Buffer, r *goff.Record) {
	buffer.WriteString(strconv.Itoa(r.Wins))
	buffer.WriteString("-")
	buffer.WriteString(strconv.Itoa(r.Losses))
	buffer.WriteString("-")
	buffer.WriteString(strconv.Itoa(r.Ties))
}

//
// Sorting
//

func (y AllYearlyLeagues) Len() int {
	return len(y)
}

func (y AllYearlyLeagues) Less(i, j int) bool {
	firstYear, _ := strconv.Atoi(y[i].Year)
	secondYear, _ := strconv.Atoi(y[j].Year)
	return firstYear > secondYear
}

func (y AllYearlyLeagues) Swap(i, j int) {
	y[i], y[j] = y[j], y[i]
}
<|endoftext|>


<commit_before>package rollbar

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"reflect"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	NAME         = "go-rollbar"
	VERSION      = "0.0.1"
	CHANNEL_SIZE = 100
)

var (
	Token       = ""
	Environment = "development"
	Endpoint    = "https://api.rollbar.com/api/1/item/"

	bodyChannel chan map[string]interface{}
	once        sync.Once
)

// -- Error reporting

func Error(level string, err error) {
	ErrorWithStackSkip(level, err, 1)
}

func ErrorWithStackSkip(level string, err error, skip int) {
	once.Do(initChannel)

	body := buildBody(level, err.Error())
	data := body["data"].(map[string]interface{})
	data["body"] = errorBody(err, skip)

	push(body)
}

// -- Message reporting

func Message(level string, msg string) {
	once.Do(initChannel)

	body := buildBody(level, msg)
	data := body["data"].(map[string]interface{})
	data["body"] = messageBody(msg)

	push(body)
}

// -- Misc.

// Build the main JSON structure that will be sent to Rollbar with the
// appropriate metadata.
func buildBody(level, title string) map[string]interface{} {
	timestamp := time.Now().Unix()
	hostname, _ := os.Hostname()
	cwd, _ := os.Getwd()

	return map[string]interface{}{
		"access_token": Token,
		"data": map[string]interface{}{
			"environment": Environment,
			"title":       title,
			"level":       level,
			"timestamp":   timestamp,
			"platform":    runtime.GOOS,
			"language":    "go",
			"server": map[string]interface{}{
				"host": hostname,
				"root": cwd,
			},
			"notifier": map[string]interface{}{
				"name":    NAME,
				"version": VERSION,
			},
		},
	}
}

// Build an error inner-body for the given error. If skip is provided, that
// number of stack trace frames will be skipped.
func errorBody(err error, skip int) map[string]interface{} {
	errorClass := reflect.TypeOf(err).String()
	if errorClass == "" {
		errorClass = "panic"
	} else {
		errorClass = strings.TrimPrefix(errorClass, "*")
	}

	return map[string]interface{}{
		"trace": map[string]interface{}{
			"frames": stacktraceFrames(3 + skip),
			"exception": map[string]interface{}{
				"class":   errorClass,
				"message": err.Error(),
			},
		},
	}
}

func messageBody(s string) map[string]interface{} {
	return map[string]interface{}{
		"message": map[string]interface{}{
			"body": s,
		},
	}
}

// -- POST handling

func initChannel() {
	bodyChannel = make(chan map[string]interface{}, CHANNEL_SIZE)

	go func() {
		for body := range bodyChannel {
			post(body)
		}
	}()
}

func push(body map[string]interface{}) {
	if len(bodyChannel) < CHANNEL_SIZE {
		bodyChannel <- body
	}
}

func post(body map[string]interface{}) {
	jsonBody, err := json.Marshal(body)
	if err != nil {
		stderr(fmt.Sprintf("Payload couldn't be encoded: %s", err.Error()))
		return
	}
	bodyReader := bytes.NewReader(jsonBody)
	resp, err := http.Post(Endpoint, "application/json", bodyReader)
	if err != nil {
		stderr(fmt.Sprintf("POST failed: %s", err.Error()))
	} else if resp.StatusCode != 200 {
		stderr(fmt.Sprintf("Rollbar response: %s", resp.Status))
	}
}
<commit_msg>Make request buffer size a variable instead of a constant.<commit_after>package rollbar

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"reflect"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	NAME         = "go-rollbar"
	VERSION      = "0.0.1"
)

var (
	Token       = ""
	Environment = "development"
	Endpoint    = "https://api.rollbar.com/api/1/item/"
	Buffer      = 100

	bodyChannel chan map[string]interface{}
	once        sync.Once
)

// -- Error reporting

func Error(level string, err error) {
	ErrorWithStackSkip(level, err, 1)
}

func ErrorWithStackSkip(level string, err error, skip int) {
	once.Do(initChannel)

	body := buildBody(level, err.Error())
	data := body["data"].(map[string]interface{})
	data["body"] = errorBody(err, skip)

	push(body)
}

// -- Message reporting

func Message(level string, msg string) {
	once.Do(initChannel)

	body := buildBody(level, msg)
	data := body["data"].(map[string]interface{})
	data["body"] = messageBody(msg)

	push(body)
}

// -- Misc.

// Build the main JSON structure that will be sent to Rollbar with the
// appropriate metadata.
func buildBody(level, title string) map[string]interface{} {
	timestamp := time.Now().Unix()
	hostname, _ := os.Hostname()
	cwd, _ := os.Getwd()

	return map[string]interface{}{
		"access_token": Token,
		"data": map[string]interface{}{
			"environment": Environment,
			"title":       title,
			"level":       level,
			"timestamp":   timestamp,
			"platform":    runtime.GOOS,
			"language":    "go",
			"server": map[string]interface{}{
				"host": hostname,
				"root": cwd,
			},
			"notifier": map[string]interface{}{
				"name":    NAME,
				"version": VERSION,
			},
		},
	}
}

// Build an error inner-body for the given error. If skip is provided, that
// number of stack trace frames will be skipped.
func errorBody(err error, skip int) map[string]interface{} {
	errorClass := reflect.TypeOf(err).String()
	if errorClass == "" {
		errorClass = "panic"
	} else {
		errorClass = strings.TrimPrefix(errorClass, "*")
	}

	return map[string]interface{}{
		"trace": map[string]interface{}{
			"frames": stacktraceFrames(3 + skip),
			"exception": map[string]interface{}{
				"class":   errorClass,
				"message": err.Error(),
			},
		},
	}
}

func messageBody(s string) map[string]interface{} {
	return map[string]interface{}{
		"message": map[string]interface{}{
			"body": s,
		},
	}
}

// -- POST handling

func initChannel() {
	bodyChannel = make(chan map[string]interface{}, Buffer)

	go func() {
		for body := range bodyChannel {
			post(body)
		}
	}()
}

func push(body map[string]interface{}) {
	if len(bodyChannel) < Buffer {
		bodyChannel <- body
	}
}

func post(body map[string]interface{}) {
	jsonBody, err := json.Marshal(body)
	if err != nil {
		stderr(fmt.Sprintf("Payload couldn't be encoded: %s", err.Error()))
		return
	}
	bodyReader := bytes.NewReader(jsonBody)
	resp, err := http.Post(Endpoint, "application/json", bodyReader)
	if err != nil {
		stderr(fmt.Sprintf("POST failed: %s", err.Error()))
	} else if resp.StatusCode != 200 {
		stderr(fmt.Sprintf("Rollbar response: %s", resp.Status))
	}
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
 *
 * 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 main

import (
	"errors"
	"net/http"

	router "github.com/gorilla/mux"
)

// newObjectLayer - initialize any object layer depending on the number of disks.
func newObjectLayer(disks, ignoredDisks []string) (ObjectLayer, error) {
	if len(disks) == 1 {
		exportPath := disks[0]
		// Initialize FS object layer.
		return newFSObjects(exportPath)
	}
	// Initialize XL object layer.
	objAPI, err := newXLObjects(disks, ignoredDisks)
	if err == errXLWriteQuorum {
		return objAPI, errors.New("Disks are different with last minio server run.")
	}
	return objAPI, err
}

// configureServer handler returns final handler for the http server.
func configureServerHandler(srvCmdConfig serverCmdConfig) http.Handler {
	objAPI, err := newObjectLayer(srvCmdConfig.disks, srvCmdConfig.ignoredDisks)
	fatalIf(err, "Unable to intialize object layer.")

	// Initialize storage rpc server.
	storageRPC, err := newRPCServer(srvCmdConfig.disks[0]) // FIXME: should only have one path.
	fatalIf(err, "Unable to initialize storage RPC server.")

	// Initialize API.
	apiHandlers := objectAPIHandlers{
		ObjectAPI: objAPI,
	}

	// Initialize Web.
	webHandlers := &webAPIHandlers{
		ObjectAPI: objAPI,
	}

	// Initialize router.
	mux := router.NewRouter()

	// Register all routers.
	registerStorageRPCRouter(mux, storageRPC)
	registerWebRouter(mux, webHandlers)
	registerAPIRouter(mux, apiHandlers)
	// Add new routers here.

	// List of some generic handlers which are applied for all
	// incoming requests.
	var handlerFns = []HandlerFunc{
		// Limits the number of concurrent http requests.
		setRateLimitHandler,
		// Adds 'crossdomain.xml' policy handler to serve legacy flash clients.
		setCrossDomainPolicy,
		// Redirect some pre-defined browser request paths to a static location prefix.
		setBrowserRedirectHandler,
		// Validates if incoming request is for restricted buckets.
		setPrivateBucketHandler,
		// Adds cache control for all browser requests.
		setBrowserCacheControlHandler,
		// Validates all incoming requests to have a valid date header.
		setTimeValidityHandler,
		// CORS setting for all browser API requests.
		setCorsHandler,
		// Validates all incoming URL resources, for invalid/unsupported
		// resources client receives a HTTP error.
		setIgnoreResourcesHandler,
		// Auth handler verifies incoming authorization headers and
		// routes them accordingly. Client receives a HTTP error for
		// invalid/unsupported signatures.
		setAuthHandler,
		// Add new handlers here.
	}

	// Register rest of the handlers.
	return registerHandlers(mux, handlerFns...)
}
<commit_msg>web-browser: disable minio browser when environmental variable MINIO_BROWSER=off (#2315)<commit_after>/*
 * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
 *
 * 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 main

import (
	"errors"
	"net/http"
	"os"
	"strings"

	router "github.com/gorilla/mux"
)

// newObjectLayer - initialize any object layer depending on the number of disks.
func newObjectLayer(disks, ignoredDisks []string) (ObjectLayer, error) {
	if len(disks) == 1 {
		exportPath := disks[0]
		// Initialize FS object layer.
		return newFSObjects(exportPath)
	}
	// Initialize XL object layer.
	objAPI, err := newXLObjects(disks, ignoredDisks)
	if err == errXLWriteQuorum {
		return objAPI, errors.New("Disks are different with last minio server run.")
	}
	return objAPI, err
}

// configureServer handler returns final handler for the http server.
func configureServerHandler(srvCmdConfig serverCmdConfig) http.Handler {
	objAPI, err := newObjectLayer(srvCmdConfig.disks, srvCmdConfig.ignoredDisks)
	fatalIf(err, "Unable to intialize object layer.")

	// Initialize storage rpc server.
	storageRPC, err := newRPCServer(srvCmdConfig.disks[0]) // FIXME: should only have one path.
	fatalIf(err, "Unable to initialize storage RPC server.")

	// Initialize API.
	apiHandlers := objectAPIHandlers{
		ObjectAPI: objAPI,
	}

	// Initialize Web.
	webHandlers := &webAPIHandlers{
		ObjectAPI: objAPI,
	}

	// Initialize router.
	mux := router.NewRouter()

	// Register all routers.
	registerStorageRPCRouter(mux, storageRPC)

	// set environmental variable MINIO_BROWSER=off to disable minio web browser.
	// By default minio web browser is enabled.
	if !strings.EqualFold(os.Getenv("MINIO_BROWSER"), "off") {
		registerWebRouter(mux, webHandlers)
	}

	registerAPIRouter(mux, apiHandlers)
	// Add new routers here.

	// List of some generic handlers which are applied for all
	// incoming requests.
	var handlerFns = []HandlerFunc{
		// Limits the number of concurrent http requests.
		setRateLimitHandler,
		// Adds 'crossdomain.xml' policy handler to serve legacy flash clients.
		setCrossDomainPolicy,
		// Redirect some pre-defined browser request paths to a static location prefix.
		setBrowserRedirectHandler,
		// Validates if incoming request is for restricted buckets.
		setPrivateBucketHandler,
		// Adds cache control for all browser requests.
		setBrowserCacheControlHandler,
		// Validates all incoming requests to have a valid date header.
		setTimeValidityHandler,
		// CORS setting for all browser API requests.
		setCorsHandler,
		// Validates all incoming URL resources, for invalid/unsupported
		// resources client receives a HTTP error.
		setIgnoreResourcesHandler,
		// Auth handler verifies incoming authorization headers and
		// routes them accordingly. Client receives a HTTP error for
		// invalid/unsupported signatures.
		setAuthHandler,
		// Add new handlers here.
	}

	// Register rest of the handlers.
	return registerHandlers(mux, handlerFns...)
}
<|endoftext|>


<commit_before>//
// Copyright 2017, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"net/url"
)

// RunnersService handles communication with the runner related methods of the
// GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type RunnersService struct {
	client *Client
}

// Runner represents a GitLab CI Runner.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type Runner struct {
	ID          int    `json:"id"`
	Description string `json:"description"`
	Active      bool   `json:"active"`
	IsShared    bool   `json:"is_shared"`
	Name        string `json:"name"`
	Online      bool   `json:"online"`
	Status      string `json:"status"`
}

// ListRunnersOptions represents the available ListRunners() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type ListRunnersOptions struct {
	ListOptions
	Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
}

// ListRunners gets a list of runners accessible by the authenticated user.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
func (s *RunnersService) ListRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	req, err := s.client.NewRequest("GET", "runners", opt, options)
	if err != nil {
		return nil, nil, err
	}

	var p []*Runner
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// ListAllRunners gets a list of all runners in the GitLab instance. Access is
// restricted to users with admin privileges.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
func (s *RunnersService) ListAllRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	req, err := s.client.NewRequest("GET", "runners/all", opt, options)
	if err != nil {
		return nil, nil, err
	}

	var p []*Runner
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// ListProjectRunners gets a list of runners accessible by the authenticated user.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
func (s *RunnersService) ListProjectRunners(pid interface{}, opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/runners", url.QueryEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var p []*Runner
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// EnableProjectRunnerOptions represents the available EnableProjectRunner()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
type EnableProjectRunnerOptions struct {
	RunnerID int `json:"runner_id"`
}

// EnableProjectRunner enables an available specific runner in the project.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
func (s *RunnersService) EnableProjectRunner(pid interface{}, opt *EnableProjectRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/runners", url.QueryEscape(project))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var p *Runner
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}
<commit_msg>Tweak the new runners service just a bit (docs mostly)<commit_after>//
// Copyright 2017, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"net/url"
)

// RunnersService handles communication with the runner related methods of the
// GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type RunnersService struct {
	client *Client
}

// Runner represents a GitLab CI Runner.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type Runner struct {
	ID          int    `json:"id"`
	Description string `json:"description"`
	Active      bool   `json:"active"`
	IsShared    bool   `json:"is_shared"`
	Name        string `json:"name"`
	Online      bool   `json:"online"`
	Status      string `json:"status"`
}

// ListRunnersOptions represents the available ListRunners() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
type ListRunnersOptions struct {
	ListOptions
	Scope *string `url:"scope,omitempty" json:"scope,omitempty"`
}

// ListRunners gets a list of runners accessible by the authenticated user.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
func (s *RunnersService) ListRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	req, err := s.client.NewRequest("GET", "runners", opt, options)
	if err != nil {
		return nil, nil, err
	}

	var rs []*Runner
	resp, err := s.client.Do(req, &rs)
	if err != nil {
		return nil, resp, err
	}

	return rs, resp, err
}

// ListAllRunners gets a list of all runners in the GitLab instance. Access is
// restricted to users with admin privileges.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-all-runners
func (s *RunnersService) ListAllRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	req, err := s.client.NewRequest("GET", "runners/all", opt, options)
	if err != nil {
		return nil, nil, err
	}

	var rs []*Runner
	resp, err := s.client.Do(req, &rs)
	if err != nil {
		return nil, resp, err
	}

	return rs, resp, err
}

// ListProjectRunnersOptions represents the available ListProjectRunners()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners
type ListProjectRunnersOptions ListRunnersOptions

// ListProjectRunners gets a list of runners accessible by the authenticated user.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners
func (s *RunnersService) ListProjectRunners(pid interface{}, opt *ListProjectRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/runners", url.QueryEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var rs []*Runner
	resp, err := s.client.Do(req, &rs)
	if err != nil {
		return nil, resp, err
	}

	return rs, resp, err
}

// EnableProjectRunnerOptions represents the available EnableProjectRunner()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
type EnableProjectRunnerOptions struct {
	RunnerID int `json:"runner_id"`
}

// EnableProjectRunner enables an available specific runner in the project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
func (s *RunnersService) EnableProjectRunner(pid interface{}, opt *EnableProjectRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/runners", url.QueryEscape(project))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var r *Runner
	resp, err := s.client.Do(req, &r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}
<|endoftext|>


<commit_before>//
// data.go
//
// Created by Frederic DELBOS - fred@hyperboloide.com on Nov  9 2014.
//

package rw

import (
	"sync"
)

type Data struct {
	sync.RWMutex
	data map[string]interface{}
}

func NewData() *Data {
	return &Data{
		data: make(map[string]interface{}),
	}
}

func (d *Data) Get(key string) interface{} {
	d.RLock()
	defer d.RUnlock()
	return d.data[key]
}

func (d *Data) Set(key string, value interface{}) {
	d.Lock()
	defer d.Unlock()
	d.data[key] = value
}
<commit_msg>return error when not found<commit_after>//
// data.go
//
// Created by Frederic DELBOS - fred@hyperboloide.com on Nov  9 2014.
//

package rw

import (
	"errors"
	"sync"
)

type Data struct {
	sync.RWMutex
	data map[string]interface{}
}

func NewData() *Data {
	return &Data{
		data: make(map[string]interface{}),
	}
}

func (d *Data) Get(key string) (interface{}, error) {
	d.RLock()
	defer d.RUnlock()
	v, exists := d.data[key]
	if exists == false {
		return nil, errors.New("Data '" + key + "' not found")
	}
	return v, nil
}

func (d *Data) Set(key string, value interface{}) {
	d.Lock()
	defer d.Unlock()
	d.data[key] = value
}
<|endoftext|>


<commit_before>//Copyright 2014 Aaron Goldman. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file
package fuse

import (
	"io/ioutil"
	"testing"
)

const mountpiont = "../../mountpiont"

func TestPwd(t *testing.T) {
	fileInfos, _ := ioutil.ReadDir(mountpiont)
	t.Logf("pwd: %s", fileInfos)
}
<commit_msg>Made test for File System<commit_after>//Copyright 2014 Aaron Goldman. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file
package fuse

import (
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
)

const mountpiont = "../../mountpiont"

func TestPwd(t *testing.T) {
	fileInfos, _ := ioutil.ReadDir(mountpiont)
	t.Logf("pwd: %s", fileInfos)
}

func TestFileFunctions(t *testing.T) {

	const testFile = "textfile.txt"

	fileInfos, err := ioutil.ReadDir(mountpoint)
	if err != nil {
		t.Errorf("Testing Directory Existence Error - %s /n", err)
	}
	for fileInfo := range fileInfos {
		if fileInfo == testFile {
			t.ErrorF("File Creation Error - File Already Exists")
			return
		}
	}
	path := filepath.Join(mountpoint, testfile)
	ioutil.WriteFile(path, []byte(""), 0777)
	fileFound := false
	for fileInfo := range fileInfos {
		if fileInfo == testFile {
			fileFound = true
		}
	}
	if fileFound != true {
		t.Errorf("File Creation Error - Creation Failed")
		return
	}

	inBytes = "Test Data"
	ioutil.WriteFile(path, []byte(inOutBytes), 0777)
	outBytes, err = ioutil.ReadFile(path, []byte, 0777)
	if err != nil {
		t.errorf("Read File Error - %s", err)
		return
	}
	if !bytes.Equal(inBytes, outBytes) {
		t.errorf("Data Write Error - Data Read Not Equal to Data Written")
		return
	}

	err = os.Remove(path)
	if err != nil {
		t.errorf("File Deletion Error - %s", err)
		return
	}

}
<|endoftext|>


<commit_before>package archiver

import (
	"context"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/restic/restic/internal/debug"
	"github.com/restic/restic/internal/fs"
	"github.com/restic/restic/internal/restic"
)

// TestSnapshot creates a new snapshot of path.
func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *restic.ID) *restic.Snapshot {
	arch := New(repo, fs.Local{}, Options{})
	opts := SnapshotOptions{
		Time:     time.Now(),
		Hostname: "localhost",
		Tags:     []string{"test"},
	}
	if parent != nil {
		opts.ParentSnapshot = *parent
	}
	sn, _, err := arch.Snapshot(context.TODO(), []string{path}, opts)
	if err != nil {
		t.Fatal(err)
	}
	return sn
}

// TestDir describes a directory structure to create for a test.
type TestDir map[string]interface{}

func (d TestDir) String() string {
	return "<Dir>"
}

// TestFile describes a file created for a test.
type TestFile struct {
	Content string
}

func (f TestFile) String() string {
	return "<File>"
}

// TestSymlink describes a symlink created for a test.
type TestSymlink struct {
	Target string
}

func (s TestSymlink) String() string {
	return "<Symlink>"
}

// TestCreateFiles creates a directory structure described by dir at target,
// which must already exist. On Windows, symlinks aren't created.
func TestCreateFiles(t testing.TB, target string, dir TestDir) {
	t.Helper()
	for name, item := range dir {
		targetPath := filepath.Join(target, name)

		switch it := item.(type) {
		case TestFile:
			err := ioutil.WriteFile(targetPath, []byte(it.Content), 0644)
			if err != nil {
				t.Fatal(err)
			}
		case TestSymlink:
			if runtime.GOOS == "windows" {
				continue
			}

			err := fs.Symlink(filepath.FromSlash(it.Target), targetPath)
			if err != nil {
				t.Fatal(err)
			}
		case TestDir:
			err := fs.Mkdir(targetPath, 0755)
			if err != nil {
				t.Fatal(err)
			}

			TestCreateFiles(t, targetPath, it)
		}
	}
}

// TestWalkFunc is used by TestWalkFiles to traverse the dir. When an error is
// returned, traversal stops and the surrounding test is marked as failed.
type TestWalkFunc func(path string, item interface{}) error

// TestWalkFiles runs fn for each file/directory in dir, the filename will be
// constructed with target as the prefix. Symlinks on Windows are ignored.
func TestWalkFiles(t testing.TB, target string, dir TestDir, fn TestWalkFunc) {
	t.Helper()
	for name, item := range dir {
		targetPath := filepath.Join(target, name)

		err := fn(targetPath, item)
		if err != nil {
			t.Fatalf("TestWalkFunc returned error for %v: %v", targetPath, err)
			return
		}

		if dir, ok := item.(TestDir); ok {
			TestWalkFiles(t, targetPath, dir, fn)
		}
	}
}

// fixpath removes UNC paths (starting with `\\?`) on windows. On Linux, it's a noop.
func fixpath(item string) string {
	if runtime.GOOS != "windows" {
		return item
	}
	if strings.HasPrefix(item, `\\?`) {
		return item[4:]
	}
	return item
}

// TestEnsureFiles tests if the directory structure at target is the same as
// described in dir.
func TestEnsureFiles(t testing.TB, target string, dir TestDir) {
	t.Helper()
	pathsChecked := make(map[string]struct{})

	// first, test that all items are there
	TestWalkFiles(t, target, dir, func(path string, item interface{}) error {
		// ignore symlinks on Windows
		if _, ok := item.(TestSymlink); ok && runtime.GOOS == "windows" {
			// mark paths and parents as checked
			pathsChecked[path] = struct{}{}
			for parent := filepath.Dir(path); parent != target; parent = filepath.Dir(parent) {
				pathsChecked[parent] = struct{}{}
			}
			return nil
		}

		fi, err := fs.Lstat(path)
		if err != nil {
			return err
		}

		switch node := item.(type) {
		case TestDir:
			if !fi.IsDir() {
				t.Errorf("is not a directory: %v", path)
			}
			return nil
		case TestFile:
			if !fs.IsRegularFile(fi) {
				t.Errorf("is not a regular file: %v", path)
				return nil
			}

			content, err := ioutil.ReadFile(path)
			if err != nil {
				return err
			}

			if string(content) != node.Content {
				t.Errorf("wrong content for %v, want %q, got %q", path, node.Content, content)
			}
		case TestSymlink:
			if fi.Mode()&os.ModeType != os.ModeSymlink {
				t.Errorf("is not a symlink: %v", path)
				return nil
			}

			target, err := fs.Readlink(path)
			if err != nil {
				return err
			}

			if target != node.Target {
				t.Errorf("wrong target for %v, want %v, got %v", path, node.Target, target)
			}
		}

		pathsChecked[path] = struct{}{}

		for parent := filepath.Dir(path); parent != target; parent = filepath.Dir(parent) {
			pathsChecked[parent] = struct{}{}
		}

		return nil
	})

	// then, traverse the directory again, looking for additional files
	err := fs.Walk(target, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		path = fixpath(path)

		if path == target {
			return nil
		}

		_, ok := pathsChecked[path]
		if !ok {
			t.Errorf("additional item found: %v %v", path, fi.Mode())
		}

		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
}

// TestEnsureFileContent checks if the file in the repo is the same as file.
func TestEnsureFileContent(ctx context.Context, t testing.TB, repo restic.Repository, filename string, node *restic.Node, file TestFile) {
	if int(node.Size) != len(file.Content) {
		t.Fatalf("%v: wrong node size: want %d, got %d", filename, node.Size, len(file.Content))
		return
	}

	content := make([]byte, restic.CiphertextLength(len(file.Content)))
	pos := 0
	for _, id := range node.Content {
		part, err := repo.LoadBlob(ctx, restic.DataBlob, id, content[pos:])
		if err != nil {
			t.Fatalf("error loading blob %v: %v", id.Str(), err)
			return
		}

		pos += len(part)
	}

	content = content[:pos]

	if string(content) != file.Content {
		t.Fatalf("%v: wrong content returned, want %q, got %q", filename, file.Content, content)
	}
}

// TestEnsureTree checks that the tree ID in the repo matches dir. On Windows,
// Symlinks are ignored.
func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo restic.Repository, treeID restic.ID, dir TestDir) {
	t.Helper()

	tree, err := repo.LoadTree(ctx, treeID)
	if err != nil {
		t.Fatal(err)
		return
	}

	var nodeNames []string
	for _, node := range tree.Nodes {
		nodeNames = append(nodeNames, node.Name)
	}
	debug.Log("%v (%v) %v", prefix, treeID.Str(), nodeNames)

	checked := make(map[string]struct{})
	for _, node := range tree.Nodes {
		nodePrefix := path.Join(prefix, node.Name)

		entry, ok := dir[node.Name]
		if !ok {
			t.Errorf("unexpected tree node %q found, want: %#v", node.Name, dir)
			return
		}

		checked[node.Name] = struct{}{}

		switch e := entry.(type) {
		case TestDir:
			if node.Type != "dir" {
				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "dir")
				return
			}

			if node.Subtree == nil {
				t.Errorf("tree node %v has nil subtree", nodePrefix)
				return
			}

			TestEnsureTree(ctx, t, path.Join(prefix, node.Name), repo, *node.Subtree, e)
		case TestFile:
			if node.Type != "file" {
				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file")
			}
			TestEnsureFileContent(ctx, t, repo, nodePrefix, node, e)
		case TestSymlink:
			// skip symlinks on windows
			if runtime.GOOS == "windows" {
				continue
			}
			if node.Type != "symlink" {
				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file")
			}

			if e.Target != node.LinkTarget {
				t.Errorf("symlink %v has wrong target, want %q, got %q", nodePrefix, e.Target, node.LinkTarget)
			}
		}
	}

	for name := range dir {
		// skip checking symlinks on Windows
		entry := dir[name]
		if _, ok := entry.(TestSymlink); ok && runtime.GOOS == "windows" {
			continue
		}

		_, ok := checked[name]
		if !ok {
			t.Errorf("tree %v: expected node %q not found, has: %v", prefix, name, nodeNames)
		}
	}
}

// TestEnsureSnapshot tests if the snapshot in the repo has exactly the same
// structure as dir. On Windows, Symlinks are ignored.
func TestEnsureSnapshot(t testing.TB, repo restic.Repository, snapshotID restic.ID, dir TestDir) {
	t.Helper()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	sn, err := restic.LoadSnapshot(ctx, repo, snapshotID)
	if err != nil {
		t.Fatal(err)
		return
	}

	if sn.Tree == nil {
		t.Fatal("snapshot has nil tree ID")
		return
	}

	TestEnsureTree(ctx, t, "/", repo, *sn.Tree, dir)
}
<commit_msg>archiver: Fix test<commit_after>package archiver

import (
	"context"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/restic/restic/internal/debug"
	"github.com/restic/restic/internal/fs"
	"github.com/restic/restic/internal/restic"
)

// TestSnapshot creates a new snapshot of path.
func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *restic.ID) *restic.Snapshot {
	arch := New(repo, fs.Local{}, Options{})
	opts := SnapshotOptions{
		Time:     time.Now(),
		Hostname: "localhost",
		Tags:     []string{"test"},
	}
	if parent != nil {
		opts.ParentSnapshot = *parent
	}
	sn, _, err := arch.Snapshot(context.TODO(), []string{path}, opts)
	if err != nil {
		t.Fatal(err)
	}
	return sn
}

// TestDir describes a directory structure to create for a test.
type TestDir map[string]interface{}

func (d TestDir) String() string {
	return "<Dir>"
}

// TestFile describes a file created for a test.
type TestFile struct {
	Content string
}

func (f TestFile) String() string {
	return "<File>"
}

// TestSymlink describes a symlink created for a test.
type TestSymlink struct {
	Target string
}

func (s TestSymlink) String() string {
	return "<Symlink>"
}

// TestCreateFiles creates a directory structure described by dir at target,
// which must already exist. On Windows, symlinks aren't created.
func TestCreateFiles(t testing.TB, target string, dir TestDir) {
	t.Helper()
	for name, item := range dir {
		targetPath := filepath.Join(target, name)

		switch it := item.(type) {
		case TestFile:
			err := ioutil.WriteFile(targetPath, []byte(it.Content), 0644)
			if err != nil {
				t.Fatal(err)
			}
		case TestSymlink:
			if runtime.GOOS == "windows" {
				continue
			}

			err := fs.Symlink(filepath.FromSlash(it.Target), targetPath)
			if err != nil {
				t.Fatal(err)
			}
		case TestDir:
			err := fs.Mkdir(targetPath, 0755)
			if err != nil {
				t.Fatal(err)
			}

			TestCreateFiles(t, targetPath, it)
		}
	}
}

// TestWalkFunc is used by TestWalkFiles to traverse the dir. When an error is
// returned, traversal stops and the surrounding test is marked as failed.
type TestWalkFunc func(path string, item interface{}) error

// TestWalkFiles runs fn for each file/directory in dir, the filename will be
// constructed with target as the prefix. Symlinks on Windows are ignored.
func TestWalkFiles(t testing.TB, target string, dir TestDir, fn TestWalkFunc) {
	t.Helper()
	for name, item := range dir {
		targetPath := filepath.Join(target, name)

		err := fn(targetPath, item)
		if err != nil {
			t.Fatalf("TestWalkFunc returned error for %v: %v", targetPath, err)
			return
		}

		if dir, ok := item.(TestDir); ok {
			TestWalkFiles(t, targetPath, dir, fn)
		}
	}
}

// fixpath removes UNC paths (starting with `\\?`) on windows. On Linux, it's a noop.
func fixpath(item string) string {
	if runtime.GOOS != "windows" {
		return item
	}
	if strings.HasPrefix(item, `\\?`) {
		return item[4:]
	}
	return item
}

// TestEnsureFiles tests if the directory structure at target is the same as
// described in dir.
func TestEnsureFiles(t testing.TB, target string, dir TestDir) {
	t.Helper()
	pathsChecked := make(map[string]struct{})

	// first, test that all items are there
	TestWalkFiles(t, target, dir, func(path string, item interface{}) error {
		// ignore symlinks on Windows
		if _, ok := item.(TestSymlink); ok && runtime.GOOS == "windows" {
			// mark paths and parents as checked
			pathsChecked[path] = struct{}{}
			for parent := filepath.Dir(path); parent != target; parent = filepath.Dir(parent) {
				pathsChecked[parent] = struct{}{}
			}
			return nil
		}

		fi, err := fs.Lstat(path)
		if err != nil {
			return err
		}

		switch node := item.(type) {
		case TestDir:
			if !fi.IsDir() {
				t.Errorf("is not a directory: %v", path)
			}
			return nil
		case TestFile:
			if !fs.IsRegularFile(fi) {
				t.Errorf("is not a regular file: %v", path)
				return nil
			}

			content, err := ioutil.ReadFile(path)
			if err != nil {
				return err
			}

			if string(content) != node.Content {
				t.Errorf("wrong content for %v, want %q, got %q", path, node.Content, content)
			}
		case TestSymlink:
			if fi.Mode()&os.ModeType != os.ModeSymlink {
				t.Errorf("is not a symlink: %v", path)
				return nil
			}

			target, err := fs.Readlink(path)
			if err != nil {
				return err
			}

			if target != node.Target {
				t.Errorf("wrong target for %v, want %v, got %v", path, node.Target, target)
			}
		}

		pathsChecked[path] = struct{}{}

		for parent := filepath.Dir(path); parent != target; parent = filepath.Dir(parent) {
			pathsChecked[parent] = struct{}{}
		}

		return nil
	})

	// then, traverse the directory again, looking for additional files
	err := fs.Walk(target, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		path = fixpath(path)

		if path == target {
			return nil
		}

		_, ok := pathsChecked[path]
		if !ok {
			t.Errorf("additional item found: %v %v", path, fi.Mode())
		}

		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
}

// TestEnsureFileContent checks if the file in the repo is the same as file.
func TestEnsureFileContent(ctx context.Context, t testing.TB, repo restic.Repository, filename string, node *restic.Node, file TestFile) {
	if int(node.Size) != len(file.Content) {
		t.Fatalf("%v: wrong node size: want %d, got %d", filename, node.Size, len(file.Content))
		return
	}

	content := make([]byte, restic.CiphertextLength(len(file.Content)))
	pos := 0
	for _, id := range node.Content {
		part, err := repo.LoadBlob(ctx, restic.DataBlob, id, content[pos:])
		if err != nil {
			t.Fatalf("error loading blob %v: %v", id.Str(), err)
			return
		}

		copy(content[pos:pos+len(part)], part)
		pos += len(part)
	}

	content = content[:pos]

	if string(content) != file.Content {
		t.Fatalf("%v: wrong content returned, want %q, got %q", filename, file.Content, content)
	}
}

// TestEnsureTree checks that the tree ID in the repo matches dir. On Windows,
// Symlinks are ignored.
func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo restic.Repository, treeID restic.ID, dir TestDir) {
	t.Helper()

	tree, err := repo.LoadTree(ctx, treeID)
	if err != nil {
		t.Fatal(err)
		return
	}

	var nodeNames []string
	for _, node := range tree.Nodes {
		nodeNames = append(nodeNames, node.Name)
	}
	debug.Log("%v (%v) %v", prefix, treeID.Str(), nodeNames)

	checked := make(map[string]struct{})
	for _, node := range tree.Nodes {
		nodePrefix := path.Join(prefix, node.Name)

		entry, ok := dir[node.Name]
		if !ok {
			t.Errorf("unexpected tree node %q found, want: %#v", node.Name, dir)
			return
		}

		checked[node.Name] = struct{}{}

		switch e := entry.(type) {
		case TestDir:
			if node.Type != "dir" {
				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "dir")
				return
			}

			if node.Subtree == nil {
				t.Errorf("tree node %v has nil subtree", nodePrefix)
				return
			}

			TestEnsureTree(ctx, t, path.Join(prefix, node.Name), repo, *node.Subtree, e)
		case TestFile:
			if node.Type != "file" {
				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file")
			}
			TestEnsureFileContent(ctx, t, repo, nodePrefix, node, e)
		case TestSymlink:
			// skip symlinks on windows
			if runtime.GOOS == "windows" {
				continue
			}
			if node.Type != "symlink" {
				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file")
			}

			if e.Target != node.LinkTarget {
				t.Errorf("symlink %v has wrong target, want %q, got %q", nodePrefix, e.Target, node.LinkTarget)
			}
		}
	}

	for name := range dir {
		// skip checking symlinks on Windows
		entry := dir[name]
		if _, ok := entry.(TestSymlink); ok && runtime.GOOS == "windows" {
			continue
		}

		_, ok := checked[name]
		if !ok {
			t.Errorf("tree %v: expected node %q not found, has: %v", prefix, name, nodeNames)
		}
	}
}

// TestEnsureSnapshot tests if the snapshot in the repo has exactly the same
// structure as dir. On Windows, Symlinks are ignored.
func TestEnsureSnapshot(t testing.TB, repo restic.Repository, snapshotID restic.ID, dir TestDir) {
	t.Helper()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	sn, err := restic.LoadSnapshot(ctx, repo, snapshotID)
	if err != nil {
		t.Fatal(err)
		return
	}

	if sn.Tree == nil {
		t.Fatal("snapshot has nil tree ID")
		return
	}

	TestEnsureTree(ctx, t, "/", repo, *sn.Tree, dir)
}
<|endoftext|>


<commit_before>package compat

type CSSFeature uint32

const (
	HexRGBA CSSFeature = 1 << iota

	RebeccaPurple

	// This feature includes all of the following:
	// - Allow floats in rgb() and rgba()
	// - hsl() can accept alpha values
	// - rgb() can accept alpha values
	// - Space-separated functional color notations
	Modern_RGB_HSL

	InsetProperty
	Nesting
)

func (features CSSFeature) Has(feature CSSFeature) bool {
	return (features & feature) != 0
}

var cssTable = map[CSSFeature]map[Engine][]versionRange{
	// Data from: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
	HexRGBA: {
		Chrome:  {{start: v{62, 0, 0}}},
		Edge:    {{start: v{79, 0, 0}}},
		Firefox: {{start: v{49, 0, 0}}},
		IOS:     {{start: v{9, 3, 0}}},
		Safari:  {{start: v{9, 1, 0}}},
	},
	RebeccaPurple: {
		Chrome:  {{start: v{38, 0, 0}}},
		Edge:    {{start: v{12, 0, 0}}},
		Firefox: {{start: v{33, 0, 0}}},
		IOS:     {{start: v{8, 0, 0}}},
		Safari:  {{start: v{9, 0, 0}}},
	},
	Modern_RGB_HSL: {
		Chrome:  {{start: v{66, 0, 0}}},
		Edge:    {{start: v{79, 0, 0}}},
		Firefox: {{start: v{52, 0, 0}}},
		IOS:     {{start: v{12, 2, 0}}},
		Safari:  {{start: v{12, 1, 0}}},
	},

	// Data from: https://developer.mozilla.org/en-US/docs/Web/CSS/inset
	InsetProperty: {
		Chrome:  {{start: v{87, 0, 0}}},
		Edge:    {{start: v{87, 0, 0}}},
		Firefox: {{start: v{66, 0, 0}}},
		IOS:     {{start: v{14, 5, 0}}},
		Safari:  {{start: v{14, 1, 0}}},
	},

	// This isn't supported anywhere right now: https://caniuse.com/css-nesting
	Nesting: {},
}

// Return all features that are not available in at least one environment
func UnsupportedCSSFeatures(constraints map[Engine][]int) (unsupported CSSFeature) {
	for feature, engines := range cssTable {
		for engine, version := range constraints {
			if engine == ES || engine == Node {
				// Specifying "--target=es2020" shouldn't affect CSS
				continue
			}
			if versionRanges, ok := engines[engine]; !ok || !isVersionSupported(versionRanges, version) {
				unsupported |= feature
			}
		}
	}
	return
}
<commit_msg>add opera/ie to css feature table (#2170)<commit_after>package compat

type CSSFeature uint32

const (
	HexRGBA CSSFeature = 1 << iota

	RebeccaPurple

	// This feature includes all of the following:
	// - Allow floats in rgb() and rgba()
	// - hsl() can accept alpha values
	// - rgb() can accept alpha values
	// - Space-separated functional color notations
	Modern_RGB_HSL

	InsetProperty
	Nesting
)

func (features CSSFeature) Has(feature CSSFeature) bool {
	return (features & feature) != 0
}

var cssTable = map[CSSFeature]map[Engine][]versionRange{
	// Data from: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
	HexRGBA: {
		Chrome:  {{start: v{62, 0, 0}}},
		Edge:    {{start: v{79, 0, 0}}},
		Firefox: {{start: v{49, 0, 0}}},
		IOS:     {{start: v{9, 3, 0}}},
		Opera:   {{start: v{49, 0, 0}}},
		Safari:  {{start: v{9, 1, 0}}},
	},
	RebeccaPurple: {
		Chrome:  {{start: v{38, 0, 0}}},
		Edge:    {{start: v{12, 0, 0}}},
		Firefox: {{start: v{33, 0, 0}}},
		IE:      {{start: v{11, 0, 0}}},
		IOS:     {{start: v{8, 0, 0}}},
		Opera:   {{start: v{25, 0, 0}}},
		Safari:  {{start: v{9, 0, 0}}},
	},
	Modern_RGB_HSL: {
		Chrome:  {{start: v{66, 0, 0}}},
		Edge:    {{start: v{79, 0, 0}}},
		Firefox: {{start: v{52, 0, 0}}},
		IOS:     {{start: v{12, 2, 0}}},
		Opera:   {{start: v{53, 0, 0}}},
		Safari:  {{start: v{12, 1, 0}}},
	},

	// Data from: https://developer.mozilla.org/en-US/docs/Web/CSS/inset
	InsetProperty: {
		Chrome:  {{start: v{87, 0, 0}}},
		Edge:    {{start: v{87, 0, 0}}},
		Firefox: {{start: v{66, 0, 0}}},
		IOS:     {{start: v{14, 5, 0}}},
		Opera:   {{start: v{73, 0, 0}}},
		Safari:  {{start: v{14, 1, 0}}},
	},

	// This isn't supported anywhere right now: https://caniuse.com/css-nesting
	Nesting: {},
}

// Return all features that are not available in at least one environment
func UnsupportedCSSFeatures(constraints map[Engine][]int) (unsupported CSSFeature) {
	for feature, engines := range cssTable {
		for engine, version := range constraints {
			if engine == ES || engine == Node {
				// Specifying "--target=es2020" shouldn't affect CSS
				continue
			}
			if versionRanges, ok := engines[engine]; !ok || !isVersionSupported(versionRanges, version) {
				unsupported |= feature
			}
		}
	}
	return
}
<|endoftext|>


<commit_before>package consumergroup

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"gopkg.in/Shopify/sarama.v1"
)

var (
	AlreadyClosing         = errors.New("The consumer group is already shutting down.")
	FailedToClaimPartition = errors.New("Failed to claim partition for this consumer instance. Do you have a rogue consumer running?")
)

type Config struct {
	*sarama.Config

	Zookeeper struct {
		// The Zookeeper read timeout. Defaults to 1 second
		Timeout time.Duration

		// Zookeeper chroot to use. Should not include a trailing slash.
		// Leave this empty when your Kafka install does not use a chroot.
		Chroot string
	}

	Offsets struct {
		Initial           int64         // The initial offset method to use if the consumer has no previously stored offset. Must be either sarama.OffsetOldest (default) or sarama.OffsetNewest.
		ProcessingTimeout time.Duration // Time to wait for all the offsets for a partition to be processed after stopping to consume from it. Defaults to 1 minute.
		CommitInterval    time.Duration // The interval between which the prossed offsets are commited.
	}
}

func NewConfig() *Config {
	config := &Config{}
	config.Config = sarama.NewConfig()
	config.Zookeeper.Timeout = 1 * time.Second
	config.Offsets.Initial = sarama.OffsetOldest
	config.Offsets.ProcessingTimeout = 60 * time.Second
	config.Offsets.CommitInterval = 10 * time.Second

	return config
}

func (cgc *Config) Validate() error {
	if cgc.Zookeeper.Timeout <= 0 {
		return sarama.ConfigurationError("ZookeeperTimeout should have a duration > 0")
	}

	if cgc.Offsets.CommitInterval <= 0 {
		return sarama.ConfigurationError("CommitInterval should have a duration > 0")
	}

	if cgc.Offsets.Initial != sarama.OffsetOldest && cgc.Offsets.Initial != sarama.OffsetNewest {
		return errors.New("Offsets.Initial should be sarama.OffsetOldest or sarama.OffsetNewest.")
	}

	if cgc.Config != nil {
		if err := cgc.Config.Validate(); err != nil {
			return err
		}
	}

	return nil
}

// The ConsumerGroup type holds all the information for a consumer that is part
// of a consumer group. Call JoinConsumerGroup to start a consumer.
type ConsumerGroup struct {
	id, name string

	config *Config

	consumer sarama.Consumer
	zk       *ZK

	wg             sync.WaitGroup
	singleShutdown sync.Once

	messages chan *sarama.ConsumerMessage
	errors   chan *sarama.ConsumerError
	stopper  chan struct{}

	brokers   map[int]string
	consumers []string

	offsetManager OffsetManager
}

// Connects to a consumer group, using Zookeeper for auto-discovery
func JoinConsumerGroup(name string, topics []string, zookeeper []string, config *Config) (cg *ConsumerGroup, err error) {

	if name == "" {
		return nil, sarama.ConfigurationError("Empty consumergroup name")
	}

	if len(topics) == 0 {
		return nil, sarama.ConfigurationError("No topics provided")
	}

	if len(zookeeper) == 0 {
		return nil, errors.New("You need to provide at least one zookeeper node address!")
	}

	if config == nil {
		config = NewConfig()
	}
	config.ClientID = name

	// Validate configuration
	if err = config.Validate(); err != nil {
		return
	}

	var zk *ZK
	if zk, err = NewZK(zookeeper, config.Zookeeper.Chroot, config.Zookeeper.Timeout); err != nil {
		return
	}

	brokers, err := zk.Brokers()
	if err != nil {
		zk.Close()
		return
	}

	brokerList := make([]string, 0, len(brokers))
	for _, broker := range brokers {
		brokerList = append(brokerList, broker)
	}

	var consumer sarama.Consumer
	if consumer, err = sarama.NewConsumer(brokerList, config.Config); err != nil {
		zk.Close()
		return
	}

	var consumerID string
	consumerID, err = generateConsumerID()
	if err != nil {
		consumer.Close()
		zk.Close()
		return
	}

	cg = &ConsumerGroup{
		id:       consumerID,
		name:     name,
		config:   config,
		brokers:  brokers,
		consumer: consumer,
		zk:       zk,
		messages: make(chan *sarama.ConsumerMessage, config.ChannelBufferSize),
		errors:   make(chan *sarama.ConsumerError, config.ChannelBufferSize),
		stopper:  make(chan struct{}),
	}

	// Register consumer group
	if err = zk.RegisterGroup(name); err != nil {
		cg.Logf("FAILED to register consumer group: %s!\n", err)
		return
	}

	// Register itself with zookeeper
	if err = zk.RegisterConsumer(name, consumerID, topics); err != nil {
		cg.Logf("FAILED to register consumer instance: %s!\n", err)
		return
	} else {
		cg.Logf("Consumer instance registered (%s).", cg.id)
	}

	offsetConfig := OffsetManagerConfig{CommitInterval: config.Offsets.CommitInterval}
	cg.offsetManager = NewZookeeperOffsetManager(cg, &offsetConfig)

	go cg.topicListConsumer(topics)

	return
}

// Returns a channel that you can read to obtain events from Kafka to process.
func (cg *ConsumerGroup) Messages() <-chan *sarama.ConsumerMessage {
	return cg.messages
}

// Returns a channel that you can read to obtain events from Kafka to process.
func (cg *ConsumerGroup) Errors() <-chan *sarama.ConsumerError {
	return cg.errors
}

func (cg *ConsumerGroup) Closed() bool {
	return cg.id == ""
}

func (cg *ConsumerGroup) Close() error {
	shutdownError := AlreadyClosing
	cg.singleShutdown.Do(func() {
		defer cg.zk.Close()

		shutdownError = nil

		close(cg.stopper)
		cg.wg.Wait()

		if err := cg.offsetManager.Close(); err != nil {
			cg.Logf("FAILED closing the offset manager: %s!\n", err)
		}

		if shutdownError = cg.zk.DeregisterConsumer(cg.name, cg.id); shutdownError != nil {
			cg.Logf("FAILED deregistering consumer instance: %s!\n", shutdownError)
		} else {
			cg.Logf("Deregistered consumer instance %s.\n", cg.id)
		}

		if shutdownError = cg.consumer.Close(); shutdownError != nil {
			cg.Logf("FAILED closing the Sarama client: %s\n", shutdownError)
		}

		close(cg.messages)
		close(cg.errors)
		cg.id = ""

	})

	return shutdownError
}

func (cg *ConsumerGroup) Logf(format string, args ...interface{}) {
	var identifier string
	if cg.id == "" {
		identifier = "(defunct)"
	} else {
		identifier = cg.id[len(cg.id)-12:]
	}
	sarama.Logger.Printf("[%s/%s] %s", cg.name, identifier, fmt.Sprintf(format, args...))
}

func (cg *ConsumerGroup) CommitUpto(message *sarama.ConsumerMessage) error {
	cg.offsetManager.MarkAsProcessed(message.Topic, message.Partition, message.Offset)
	return nil
}

func (cg *ConsumerGroup) topicListConsumer(topics []string) {
	for {
		select {
		case <-cg.stopper:
			return
		default:
		}

		consumers, consumerChanges, err := cg.zk.Consumers(cg.name)
		if err != nil {
			cg.Logf("FAILED to get list of registered consumer instances: %s\n", err)
			return
		}

		cg.consumers = consumers
		cg.Logf("Currently registered consumers: %d\n", len(cg.consumers))

		stopper := make(chan struct{})

		for _, topic := range topics {
			cg.wg.Add(1)
			go cg.topicConsumer(topic, cg.messages, cg.errors, stopper)
		}

		select {
		case <-cg.stopper:
			close(stopper)
			return

		case <-consumerChanges:
			cg.Logf("Triggering rebalance due to consumer list change\n")
			close(stopper)
			cg.wg.Wait()
		}
	}
}

func (cg *ConsumerGroup) topicConsumer(topic string, messages chan<- *sarama.ConsumerMessage, errors chan<- *sarama.ConsumerError, stopper <-chan struct{}) {
	defer cg.wg.Done()

	select {
	case <-stopper:
		return
	default:
	}

	cg.Logf("%s :: Started topic consumer\n", topic)

	// Fetch a list of partition IDs
	partitions, err := cg.zk.Partitions(topic)
	if err != nil {
		cg.Logf("%s :: FAILED to get list of partitions: %s\n", topic, err)
		return
	}

	dividedPartitions := dividePartitionsBetweenConsumers(cg.consumers, partitions)
	myPartitions := dividedPartitions[cg.id]
	cg.Logf("%s :: Claiming %d of %d partitions", topic, len(myPartitions), len(partitions))

	// Consume all the assigned partitions
	var wg sync.WaitGroup
	for _, pid := range myPartitions {

		wg.Add(1)
		go cg.partitionConsumer(topic, pid.id, messages, errors, &wg, stopper)
	}

	wg.Wait()
	cg.Logf("%s :: Stopped topic consumer\n", topic)
}

// Consumes a partition
func (cg *ConsumerGroup) partitionConsumer(topic string, partition int32, messages chan<- *sarama.ConsumerMessage, errors chan<- *sarama.ConsumerError, wg *sync.WaitGroup, stopper <-chan struct{}) {
	defer wg.Done()

	select {
	case <-stopper:
		return
	default:
	}

	err := cg.zk.Claim(cg.name, topic, partition, cg.id)
	if err != nil {
		cg.Logf("%s/%d :: FAILED to claim the partition: %s\n", topic, partition, err)
		return
	}
	defer cg.zk.Release(cg.name, topic, partition, cg.id)

	nextOffset, err := cg.offsetManager.InitializePartition(topic, partition)
	if err != nil {
		cg.Logf("%s/%d :: FAILED to determine initial offset: %s\n", topic, partition, err)
		return
	}

	if nextOffset > 0 {
		cg.Logf("%s/%d :: Partition consumer starting at offset %d.\n", topic, partition, nextOffset)
	} else {
		nextOffset = cg.config.Offsets.Initial
		if nextOffset == sarama.OffsetOldest {
			cg.Logf("%s/%d :: Partition consumer starting at the oldest available offset.\n", topic, partition)
		} else if nextOffset == sarama.OffsetNewest {
			cg.Logf("%s/%d :: Partition consumer listening for new messages only.\n", topic, partition)
		}
	}

	consumer, err := cg.consumer.ConsumePartition(topic, partition, nextOffset)
	if err != nil {
		cg.Logf("%s/%d :: FAILED to start partition consumer: %s\n", topic, partition, err)
		return
	}
	defer consumer.Close()

	err = nil
	var lastOffset int64 = -1 // aka unknown
partitionConsumerLoop:
	for {
		select {
		case <-stopper:
			break partitionConsumerLoop

		case err := <-consumer.Errors():
			for {
				select {
				case errors <- err:
					continue partitionConsumerLoop

				case <-stopper:
					break partitionConsumerLoop
				}
			}

		case message := <-consumer.Messages():
			for {
				select {
				case <-stopper:
					break partitionConsumerLoop

				case messages <- message:
					lastOffset = message.Offset
					continue partitionConsumerLoop
				}
			}
		}
	}

	cg.Logf("%s/%d :: Stopping partition consumer at offset %d\n", topic, partition, lastOffset)
	if err := cg.offsetManager.FinalizePartition(topic, partition, lastOffset, cg.config.Offsets.ProcessingTimeout); err != nil {
		cg.Logf("%s/%d :: %s\n", topic, partition, err)
	}
}
<commit_msg>JoinConsumerGroup silently fails if topics are not created #42<commit_after>package consumergroup

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"gopkg.in/Shopify/sarama.v1"
)

var (
	AlreadyClosing         = errors.New("The consumer group is already shutting down.")
	FailedToClaimPartition = errors.New("Failed to claim partition for this consumer instance. Do you have a rogue consumer running?")
)

type Config struct {
	*sarama.Config

	Zookeeper struct {
		// The Zookeeper read timeout. Defaults to 1 second
		Timeout time.Duration

		// Zookeeper chroot to use. Should not include a trailing slash.
		// Leave this empty when your Kafka install does not use a chroot.
		Chroot string
	}

	Offsets struct {
		Initial           int64         // The initial offset method to use if the consumer has no previously stored offset. Must be either sarama.OffsetOldest (default) or sarama.OffsetNewest.
		ProcessingTimeout time.Duration // Time to wait for all the offsets for a partition to be processed after stopping to consume from it. Defaults to 1 minute.
		CommitInterval    time.Duration // The interval between which the prossed offsets are commited.
	}
}

func NewConfig() *Config {
	config := &Config{}
	config.Config = sarama.NewConfig()
	config.Zookeeper.Timeout = 1 * time.Second
	config.Offsets.Initial = sarama.OffsetOldest
	config.Offsets.ProcessingTimeout = 60 * time.Second
	config.Offsets.CommitInterval = 10 * time.Second

	return config
}

func (cgc *Config) Validate() error {
	if cgc.Zookeeper.Timeout <= 0 {
		return sarama.ConfigurationError("ZookeeperTimeout should have a duration > 0")
	}

	if cgc.Offsets.CommitInterval <= 0 {
		return sarama.ConfigurationError("CommitInterval should have a duration > 0")
	}

	if cgc.Offsets.Initial != sarama.OffsetOldest && cgc.Offsets.Initial != sarama.OffsetNewest {
		return errors.New("Offsets.Initial should be sarama.OffsetOldest or sarama.OffsetNewest.")
	}

	if cgc.Config != nil {
		if err := cgc.Config.Validate(); err != nil {
			return err
		}
	}

	return nil
}

// The ConsumerGroup type holds all the information for a consumer that is part
// of a consumer group. Call JoinConsumerGroup to start a consumer.
type ConsumerGroup struct {
	id, name string

	config *Config

	consumer sarama.Consumer
	zk       *ZK

	wg             sync.WaitGroup
	singleShutdown sync.Once

	messages chan *sarama.ConsumerMessage
	errors   chan *sarama.ConsumerError
	stopper  chan struct{}

	brokers   map[int]string
	consumers []string

	offsetManager OffsetManager
}

// Connects to a consumer group, using Zookeeper for auto-discovery
func JoinConsumerGroup(name string, topics []string, zookeeper []string, config *Config) (cg *ConsumerGroup, err error) {

	if name == "" {
		return nil, sarama.ConfigurationError("Empty consumergroup name")
	}

	if len(topics) == 0 {
		return nil, sarama.ConfigurationError("No topics provided")
	}

	if len(zookeeper) == 0 {
		return nil, errors.New("You need to provide at least one zookeeper node address!")
	}

	if config == nil {
		config = NewConfig()
	}
	config.ClientID = name

	// Validate configuration
	if err = config.Validate(); err != nil {
		return
	}

	var zk *ZK
	if zk, err = NewZK(zookeeper, config.Zookeeper.Chroot, config.Zookeeper.Timeout); err != nil {
		return
	}

	brokers, err := zk.Brokers()
	if err != nil {
		zk.Close()
		return
	}

	brokerList := make([]string, 0, len(brokers))
	for _, broker := range brokers {
		brokerList = append(brokerList, broker)
	}

	var consumer sarama.Consumer
	if consumer, err = sarama.NewConsumer(brokerList, config.Config); err != nil {
		zk.Close()
		return
	}

	var consumerID string
	consumerID, err = generateConsumerID()
	if err != nil {
		consumer.Close()
		zk.Close()
		return
	}

	cg = &ConsumerGroup{
		id:       consumerID,
		name:     name,
		config:   config,
		brokers:  brokers,
		consumer: consumer,
		zk:       zk,
		messages: make(chan *sarama.ConsumerMessage, config.ChannelBufferSize),
		errors:   make(chan *sarama.ConsumerError, config.ChannelBufferSize),
		stopper:  make(chan struct{}),
	}

	// Register consumer group
	if err = zk.RegisterGroup(name); err != nil {
		cg.Logf("FAILED to register consumer group: %s!\n", err)
		return
	}

	// Register itself with zookeeper
	if err = zk.RegisterConsumer(name, consumerID, topics); err != nil {
		cg.Logf("FAILED to register consumer instance: %s!\n", err)
		return
	} else {
		cg.Logf("Consumer instance registered (%s).", cg.id)
	}

	offsetConfig := OffsetManagerConfig{CommitInterval: config.Offsets.CommitInterval}
	cg.offsetManager = NewZookeeperOffsetManager(cg, &offsetConfig)

	go cg.topicListConsumer(topics)

	return
}

// Returns a channel that you can read to obtain events from Kafka to process.
func (cg *ConsumerGroup) Messages() <-chan *sarama.ConsumerMessage {
	return cg.messages
}

// Returns a channel that you can read to obtain events from Kafka to process.
func (cg *ConsumerGroup) Errors() <-chan *sarama.ConsumerError {
	return cg.errors
}

func (cg *ConsumerGroup) Closed() bool {
	return cg.id == ""
}

func (cg *ConsumerGroup) Close() error {
	shutdownError := AlreadyClosing
	cg.singleShutdown.Do(func() {
		defer cg.zk.Close()

		shutdownError = nil

		close(cg.stopper)
		cg.wg.Wait()

		if err := cg.offsetManager.Close(); err != nil {
			cg.Logf("FAILED closing the offset manager: %s!\n", err)
		}

		if shutdownError = cg.zk.DeregisterConsumer(cg.name, cg.id); shutdownError != nil {
			cg.Logf("FAILED deregistering consumer instance: %s!\n", shutdownError)
		} else {
			cg.Logf("Deregistered consumer instance %s.\n", cg.id)
		}

		if shutdownError = cg.consumer.Close(); shutdownError != nil {
			cg.Logf("FAILED closing the Sarama client: %s\n", shutdownError)
		}

		close(cg.messages)
		close(cg.errors)
		cg.id = ""

	})

	return shutdownError
}

func (cg *ConsumerGroup) Logf(format string, args ...interface{}) {
	var identifier string
	if cg.id == "" {
		identifier = "(defunct)"
	} else {
		identifier = cg.id[len(cg.id)-12:]
	}
	sarama.Logger.Printf("[%s/%s] %s", cg.name, identifier, fmt.Sprintf(format, args...))
}

func (cg *ConsumerGroup) CommitUpto(message *sarama.ConsumerMessage) error {
	cg.offsetManager.MarkAsProcessed(message.Topic, message.Partition, message.Offset)
	return nil
}

func (cg *ConsumerGroup) topicListConsumer(topics []string) {
	for {
		select {
		case <-cg.stopper:
			return
		default:
		}

		consumers, consumerChanges, err := cg.zk.Consumers(cg.name)
		if err != nil {
			cg.Logf("FAILED to get list of registered consumer instances: %s\n", err)
			return
		}

		cg.consumers = consumers
		cg.Logf("Currently registered consumers: %d\n", len(cg.consumers))

		stopper := make(chan struct{})

		for _, topic := range topics {
			cg.wg.Add(1)
			go cg.topicConsumer(topic, cg.messages, cg.errors, stopper)
		}

		select {
		case <-cg.stopper:
			close(stopper)
			return

		case <-consumerChanges:
			cg.Logf("Triggering rebalance due to consumer list change\n")
			close(stopper)
			cg.wg.Wait()
		}
	}
}

func (cg *ConsumerGroup) topicConsumer(topic string, messages chan<- *sarama.ConsumerMessage, errors chan<- *sarama.ConsumerError, stopper <-chan struct{}) {
	defer cg.wg.Done()

	select {
	case <-stopper:
		return
	default:
	}

	cg.Logf("%s :: Started topic consumer\n", topic)

	// Fetch a list of partition IDs
	partitions, err := cg.zk.Partitions(topic)
	if err != nil {
		cg.Logf("%s :: FAILED to get list of partitions: %s\n", topic, err)
		cg.errors <- &sarama.ConsumerError{
			Topic:     topic,
			Partition: 0,
			Err:       err,
		}
		return
	}

	dividedPartitions := dividePartitionsBetweenConsumers(cg.consumers, partitions)
	myPartitions := dividedPartitions[cg.id]
	cg.Logf("%s :: Claiming %d of %d partitions", topic, len(myPartitions), len(partitions))

	// Consume all the assigned partitions
	var wg sync.WaitGroup
	for _, pid := range myPartitions {

		wg.Add(1)
		go cg.partitionConsumer(topic, pid.id, messages, errors, &wg, stopper)
	}

	wg.Wait()
	cg.Logf("%s :: Stopped topic consumer\n", topic)
}

// Consumes a partition
func (cg *ConsumerGroup) partitionConsumer(topic string, partition int32, messages chan<- *sarama.ConsumerMessage, errors chan<- *sarama.ConsumerError, wg *sync.WaitGroup, stopper <-chan struct{}) {
	defer wg.Done()

	select {
	case <-stopper:
		return
	default:
	}

	err := cg.zk.Claim(cg.name, topic, partition, cg.id)
	if err != nil {
		cg.Logf("%s/%d :: FAILED to claim the partition: %s\n", topic, partition, err)
		return
	}
	defer cg.zk.Release(cg.name, topic, partition, cg.id)

	nextOffset, err := cg.offsetManager.InitializePartition(topic, partition)
	if err != nil {
		cg.Logf("%s/%d :: FAILED to determine initial offset: %s\n", topic, partition, err)
		return
	}

	if nextOffset > 0 {
		cg.Logf("%s/%d :: Partition consumer starting at offset %d.\n", topic, partition, nextOffset)
	} else {
		nextOffset = cg.config.Offsets.Initial
		if nextOffset == sarama.OffsetOldest {
			cg.Logf("%s/%d :: Partition consumer starting at the oldest available offset.\n", topic, partition)
		} else if nextOffset == sarama.OffsetNewest {
			cg.Logf("%s/%d :: Partition consumer listening for new messages only.\n", topic, partition)
		}
	}

	consumer, err := cg.consumer.ConsumePartition(topic, partition, nextOffset)
	if err != nil {
		cg.Logf("%s/%d :: FAILED to start partition consumer: %s\n", topic, partition, err)
		return
	}
	defer consumer.Close()

	err = nil
	var lastOffset int64 = -1 // aka unknown
partitionConsumerLoop:
	for {
		select {
		case <-stopper:
			break partitionConsumerLoop

		case err := <-consumer.Errors():
			for {
				select {
				case errors <- err:
					continue partitionConsumerLoop

				case <-stopper:
					break partitionConsumerLoop
				}
			}

		case message := <-consumer.Messages():
			for {
				select {
				case <-stopper:
					break partitionConsumerLoop

				case messages <- message:
					lastOffset = message.Offset
					continue partitionConsumerLoop
				}
			}
		}
	}

	cg.Logf("%s/%d :: Stopping partition consumer at offset %d\n", topic, partition, lastOffset)
	if err := cg.offsetManager.FinalizePartition(topic, partition, lastOffset, cg.config.Offsets.ProcessingTimeout); err != nil {
		cg.Logf("%s/%d :: %s\n", topic, partition, err)
	}
}
<|endoftext|>