<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"time"
	"log"
	"net/http"
	"strconv"
	"strings"
)

type DeviceInformation struct {
	DeviceName string `json: "name"`
	PushURL    string `json: "pushURL"`
	Latitude   float64 `json: "latitude"`
	Longitude  float64 `json: "longitude"`
}

type DeviceListResponse struct {
	Devices []DeviceInformation `json: "devices"`
}

func deviceListHandler(w http.ResponseWriter, r *http.Request) {

	if !IsLoggedIn(r) {
		log.Println("deviceListHandler: user not logged in")
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	loginName := GetLoginName(r)
	if loginName == "" {
		log.Println("deviceListHandler: user does not have an email address")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("."))
		return
	}

	deviceList := devicesForUser(loginName)

	if deviceList == nil {
		log.Println("deviceListHandler: device list is empty for user")
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("."))
		return
	}

	var data []byte
	var err error

	response := DeviceListResponse{deviceList}

	data, err = json.Marshal(response)
	if err != nil {
		log.Println("deviceListHandler: could not marshal data")
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("."))
		return
	}

	w.Write(data)
}

func deviceAddHandler(w http.ResponseWriter, r *http.Request) {

	if !IsLoggedIn(r) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	loginName := GetLoginName(r)
	if loginName == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("."))
		return
	}

	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	pushURL := r.FormValue("pushURL")
	if pushURL == "" {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	deviceName := r.FormValue("deviceName")
	if deviceName == "" {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	latitude := r.FormValue("latitude")
	_, err = strconv.ParseFloat(latitude, 64)
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	longitude := r.FormValue("longitude")
	_, err = strconv.ParseFloat(longitude, 64)
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	if addDevice(loginName, deviceName, pushURL, latitude, longitude) {
		w.Write([]byte("ok"))
		return
	}

	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte("."))
}

func deviceDeleteHandler(w http.ResponseWriter, r *http.Request) {

	if !IsLoggedIn(r) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
	}

	pushURL := r.FormValue("pushURL")
	if pushURL == "" {
		log.Println(err)
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
	}

	if deleteDevice(pushURL) {
		w.Write([]byte("ok"))
		return
	}

	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte("."))
}

func deviceLocationHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("device location handler!")

	if !IsLoggedIn(r) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
	}

	pushURL := r.FormValue("pushURL")

	lat, err1 := strconv.ParseFloat(r.FormValue("lat"), 64)
	lon, err2 := strconv.ParseFloat(r.FormValue("lon"), 64)
	log.Println("Got coordinates ", lat, lon)

	if err1 != nil || err2 != nil || pushURL == "" {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	if updateDeviceLocation(pushURL, lat, lon) {
		w.Write([]byte("ok"))
		return
	}

	w.WriteHeader(400)
	w.Write([]byte("Bad Request."))
	return
}

func deviceLostHandler(w http.ResponseWriter, r *http.Request) {
	if !IsLoggedIn(r) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	// check that this device is actually owned by the user
	email := GetLoginName(r)
	if email == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("."))
		return
	}

	var lostDevice DeviceInformation
	devices := devicesForUser(email)
	for _, device := range devices {
		if device.PushURL == r.FormValue("pushURL") {
			lostDevice = device
			break
		}
	}

	if lostDevice.PushURL == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Push endpoint doesn't correspond to any device!"))
	}

	body := fmt.Sprintf("version=%d", uint64(time.Now().Unix()))
	request, err := http.NewRequest("PUT", lostDevice.PushURL, strings.NewReader(body))
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Failed to PUT to endpoint!"))
	}

	request.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}

	var client http.Client
	_, err = client.Do(request)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Failed to PUT to endpoint"))
	}
}

func serveSingle(pattern string, filename string) {
	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
		log.Println("serving file " + filename);
		http.ServeFile(w, r, filename)
	})
}

func main() {

	readConfig()
	openDb()

	http.HandleFunc("/device/update/", deviceLocationHandler)
	http.HandleFunc("/device/lost/", deviceLostHandler)

	// device management
	http.HandleFunc("/device/list", deviceListHandler)
	http.HandleFunc("/device/add/", deviceAddHandler)
	http.HandleFunc("/device/delete/", deviceDeleteHandler)

	// personas
	http.HandleFunc("/auth/check",    loginCheckHandler)
	http.HandleFunc("/auth/login",    loginHandler)
	http.HandleFunc("/auth/applogin", appLoginHandler)
	http.HandleFunc("/auth/logout",   logoutHandler)

	serveSingle("/",                      "./static/index.html")
	serveSingle("/index.html",            "./static/index.html")
	serveSingle("/install.html",          "./static/install.html")
	serveSingle("/push.html",             "./static/push.html")
	serveSingle("/app.html",              "./app/index.html")
	serveSingle("/manifest.webapp",       "./app/manifest.webapp")
	serveSingle("/style.css",             "./static/style.css")
	serveSingle("/style-app.css",         "./static/style-app.css")
	serveSingle("/style-common.css",      "./static/style-common.css")
	serveSingle("/logos/64.png",          "./static/logos/64.png")
	serveSingle("/logos/128.png",         "./static/logos/128.png")
	serveSingle("/img/persona-login.png", "./static/img/persona_sign_in_black.png")
	serveSingle("/lib/mustache.js",       "./static/lib/mustache.js")

	log.Println("Listening on", gServerConfig.Hostname+":"+gServerConfig.Port)

	var err error
	if gServerConfig.UseTLS {
		err = http.ListenAndServeTLS(gServerConfig.Hostname+":"+gServerConfig.Port,
			gServerConfig.CertFilename,
			gServerConfig.KeyFilename,
			nil)
	} else {
		log.Println("This is a really unsafe way to run the push server.  Really.  Don't do this in production.")
		err = http.ListenAndServe(gServerConfig.Hostname+":"+gServerConfig.Port, nil)
	}

	log.Println("Exiting... ", err)
	closeDb()
}
<commit_msg>Use correct Content-Type for manifest<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"time"
	"log"
	"net/http"
	"strconv"
	"strings"
)

type DeviceInformation struct {
	DeviceName string `json: "name"`
	PushURL    string `json: "pushURL"`
	Latitude   float64 `json: "latitude"`
	Longitude  float64 `json: "longitude"`
}

type DeviceListResponse struct {
	Devices []DeviceInformation `json: "devices"`
}

func deviceListHandler(w http.ResponseWriter, r *http.Request) {

	if !IsLoggedIn(r) {
		log.Println("deviceListHandler: user not logged in")
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	loginName := GetLoginName(r)
	if loginName == "" {
		log.Println("deviceListHandler: user does not have an email address")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("."))
		return
	}

	deviceList := devicesForUser(loginName)

	if deviceList == nil {
		log.Println("deviceListHandler: device list is empty for user")
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("."))
		return
	}

	var data []byte
	var err error

	response := DeviceListResponse{deviceList}

	data, err = json.Marshal(response)
	if err != nil {
		log.Println("deviceListHandler: could not marshal data")
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("."))
		return
	}

	w.Write(data)
}

func deviceAddHandler(w http.ResponseWriter, r *http.Request) {

	if !IsLoggedIn(r) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	loginName := GetLoginName(r)
	if loginName == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("."))
		return
	}

	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	pushURL := r.FormValue("pushURL")
	if pushURL == "" {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	deviceName := r.FormValue("deviceName")
	if deviceName == "" {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	latitude := r.FormValue("latitude")
	_, err = strconv.ParseFloat(latitude, 64)
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	longitude := r.FormValue("longitude")
	_, err = strconv.ParseFloat(longitude, 64)
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	if addDevice(loginName, deviceName, pushURL, latitude, longitude) {
		w.Write([]byte("ok"))
		return
	}

	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte("."))
}

func deviceDeleteHandler(w http.ResponseWriter, r *http.Request) {

	if !IsLoggedIn(r) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
	}

	pushURL := r.FormValue("pushURL")
	if pushURL == "" {
		log.Println(err)
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
	}

	if deleteDevice(pushURL) {
		w.Write([]byte("ok"))
		return
	}

	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte("."))
}

func deviceLocationHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("device location handler!")

	if !IsLoggedIn(r) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
	}

	pushURL := r.FormValue("pushURL")

	lat, err1 := strconv.ParseFloat(r.FormValue("lat"), 64)
	lon, err2 := strconv.ParseFloat(r.FormValue("lon"), 64)
	log.Println("Got coordinates ", lat, lon)

	if err1 != nil || err2 != nil || pushURL == "" {
		w.WriteHeader(400)
		w.Write([]byte("Bad Request."))
		return
	}

	if updateDeviceLocation(pushURL, lat, lon) {
		w.Write([]byte("ok"))
		return
	}

	w.WriteHeader(400)
	w.Write([]byte("Bad Request."))
	return
}

func deviceLostHandler(w http.ResponseWriter, r *http.Request) {
	if !IsLoggedIn(r) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("."))
		return
	}

	// check that this device is actually owned by the user
	email := GetLoginName(r)
	if email == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("."))
		return
	}

	var lostDevice DeviceInformation
	devices := devicesForUser(email)
	for _, device := range devices {
		if device.PushURL == r.FormValue("pushURL") {
			lostDevice = device
			break
		}
	}

	if lostDevice.PushURL == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Push endpoint doesn't correspond to any device!"))
	}

	body := fmt.Sprintf("version=%d", uint64(time.Now().Unix()))
	request, err := http.NewRequest("PUT", lostDevice.PushURL, strings.NewReader(body))
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Failed to PUT to endpoint!"))
	}

	request.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}

	var client http.Client
	_, err = client.Do(request)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Failed to PUT to endpoint"))
	}
}

func serveSingle(pattern string, filename string) {
	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
		log.Println("serving file " + filename);
		http.ServeFile(w, r, filename)
	})
}

func main() {

	readConfig()
	openDb()

	http.HandleFunc("/device/update/", deviceLocationHandler)
	http.HandleFunc("/device/lost/", deviceLostHandler)

	// device management
	http.HandleFunc("/device/list", deviceListHandler)
	http.HandleFunc("/device/add/", deviceAddHandler)
	http.HandleFunc("/device/delete/", deviceDeleteHandler)

	// personas
	http.HandleFunc("/auth/check",    loginCheckHandler)
	http.HandleFunc("/auth/login",    loginHandler)
	http.HandleFunc("/auth/applogin", appLoginHandler)
	http.HandleFunc("/auth/logout",   logoutHandler)

	serveSingle("/",                      "./static/index.html")
	serveSingle("/index.html",            "./static/index.html")
	serveSingle("/install.html",          "./static/install.html")
	serveSingle("/push.html",             "./static/push.html")
	serveSingle("/app.html",              "./app/index.html")
	serveSingle("/style.css",             "./static/style.css")
	serveSingle("/style-app.css",         "./static/style-app.css")
	serveSingle("/style-common.css",      "./static/style-common.css")
	serveSingle("/logos/64.png",          "./static/logos/64.png")
	serveSingle("/logos/128.png",         "./static/logos/128.png")
	serveSingle("/img/persona-login.png", "./static/img/persona_sign_in_black.png")
	serveSingle("/lib/mustache.js",       "./static/lib/mustache.js")

	http.HandleFunc("/manifest.webapp", func(w http.ResponseWriter, r *http.Request) {
		filename := "./app/manifest.webapp"
		log.Println("serving manifest from " + filename);

		w.Header()["Content-Type"] = []string{"application/x-web-app-manifest+json"}
		http.ServeFile(w, r, filename)
	})

	log.Println("Listening on", gServerConfig.Hostname+":"+gServerConfig.Port)

	var err error
	if gServerConfig.UseTLS {
		err = http.ListenAndServeTLS(gServerConfig.Hostname+":"+gServerConfig.Port,
			gServerConfig.CertFilename,
			gServerConfig.KeyFilename,
			nil)
	} else {
		log.Println("This is a really unsafe way to run the push server.  Really.  Don't do this in production.")
		err = http.ListenAndServe(gServerConfig.Hostname+":"+gServerConfig.Port, nil)
	}

	log.Println("Exiting... ", err)
	closeDb()
}
<|endoftext|>


<commit_before>package uishow

import (
	"errors"
	"fmt"
	ui "github.com/gizak/termui" // <- ui shortcut, optional
	"github.com/urfave/cli"
	log "github.com/wupeaking/logrus"
	kafka "github.com/wupeaking/sarama"
	"strings"
	"time"
)

var renderCallBacks = make([]func() error, 0)

func registerRenderCallBacks(cb func() error) {
	renderCallBacks = append(renderCallBacks, cb)
}

func renderInit() {
	err := ui.Init()
	if err != nil {
		panic(err)
	}

	// handle key q pressing
	ui.Handle("/sys/kbd/q", func(ui.Event) {
		// press q to quit
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/C-x", func(ui.Event) {
		// handle Ctrl + x combination
	})

	ui.Handle("/sys/kbd", func(ui.Event) {
		// handle all other key pressing
		ui.StopLoop()
		ui.Close()
	})
	ui.Handle("/timer/1s", func(e ui.Event) {
		for _, cb := range renderCallBacks {
			cb()
		}
	})
}

//此块区域的左上角和有下角坐标
func renderPar(tx, ty, bx, by int) (int, int, int, int) {
	//p := ui.NewPar("有边框文本")
	//p.Height = 3
	//p.Width = 50
	//p.TextFgColor = ui.ColorWhite
	////
	//p.BorderLabel = "介绍"
	//p.BorderFg = ui.ColorCyan

	// 再创建一个无边框
	p2 := ui.NewPar("     kafkainfo 是一个简单的调试工具（按任意键退出）")
	p2.Border = false
	p2.X = tx
	p2.Y = by + 1
	p2.Height = 3
	p2.TextFgColor = ui.ColorCyan
	p2.Width = 60

	ui.Render(p2) // feel free to call Render, it's async and non-block

	registerRenderCallBacks(func() error {
		p2.Border = !p2.Border
		p2.BorderLabel = time.Now().String()
		ui.Render(p2)
		return nil
	})

	return p2.X, p2.Y, p2.X + p2.Width, p2.Y + p2.Height

}

// 渲染所有的broker 返回 此块区域的左上角和有下角坐标
func renderBrokerInfo(brokerInfo map[string]string, tx, ty, bx, by int) (int, int, int, int) {
	addrs := make([]string, 0)
	ids := make([]string, 0)

	for addr, id := range brokerInfo {
		addrs = append(addrs, addr)
		ids = append(ids, id)
	}

	addrList := ui.NewList()
	addrList.Items = addrs
	addrList.BorderFg = ui.ColorYellow
	addrList.ItemFgColor = ui.ColorCyan
	addrList.BorderLabel = "kafka broker addr list"
	addrList.Height = len(addrs) + 3
	addrList.Width = 50
	addrList.Y = by + 1
	addrList.X = tx

	idList := ui.NewList()
	idList.Items = ids
	idList.BorderFg = ui.ColorYellow
	idList.ItemFgColor = ui.ColorCyan
	idList.BorderLabel = "kafka broker id list"
	idList.Height = len(addrs) + 3
	idList.Width = 50
	idList.Y = addrList.Y
	idList.X = addrList.X + addrList.Width

	ui.Render(addrList, idList)

	return addrList.X, addrList.Y, addrList.X + addrList.Width, addrList.Y + addrList.Height
}

// 渲染topic信息
// topic名称 | 分区数量 每个分区的leader
func renderTopicInfo(topicinfo map[string]int, partsLeader map[string][]string, tx, ty, bx, by int) (int, int, int, int) {

	infos := make([]string, 0)
	infos = append(infos, "主题 | 分区数量 | 分区leader地址")
	for topic, num := range topicinfo {
		info := fmt.Sprintf("%s | %d | %s", topic, num, strings.Join(partsLeader[topic], " "))
		infos = append(infos, info)
	}
	infoList := ui.NewList()
	infoList.Items = infos
	infoList.BorderFg = ui.ColorYellow
	infoList.ItemFgColor = ui.ColorCyan
	infoList.BorderLabel = "kafka broker topics info list"
	infoList.Height = len(infos) + 3
	infoList.Width = 100
	infoList.Y = by + 1
	infoList.X = tx

	ui.Render(infoList)
	return infoList.X, infoList.Y, infoList.X + infoList.Width, infoList.Y + infoList.Height
}

func renderList() {

	strs := []string{
		"[0] github.com/gizak/termui",
		"[1] [你好，世界](fg-blue)",
		"[2] [こんにちは世界](fg-red)",
		"[3] [color output](fg-white,bg-green)",
		"[4] output.go",
		"[5] random_out.go",
		"[6] dashboard.go",
		"[7] nsf/termbox-go"}
	list := ui.NewList()
	list.Items = strs
	list.BorderFg = ui.ColorYellow
	list.ItemFgColor = ui.ColorCyan
	list.BorderLabel = "列表示例"
	list.Height = 8
	list.Width = 50
	list.Y = 5
	ui.Render(list)
	ui.Handle("/sys/kdb/l", func(ui.Event) {
		// 当按下l键时  更新一些参数 重新渲染
		list.Items[3] = "[3] [l键被按下](fg-white,bg-green)"
		ui.Render(list)
	})
}

func renderLoop() {
	ui.Loop()
}

func getAllTopics(kafkaCli kafka.Client) []string {
	topics, err := kafkaCli.Topics()

	if err != nil {
		return nil
	} else {
		return topics
	}
	//brokers := kafkaCli.Brokers()
	//
	//for i, broker := range brokers {
	//	log.WithField("index:", i).Info("id: ", broker.ID, " addr: ", broker.Addr())
	//}
	//return nil
}

// 获取所有的topic对应的分区数量
func getTopicsInfo(kafkaCli kafka.Client, topics []string) map[string]int {
	info := make(map[string]int)
	for _, topic := range topics {
		parts, err := kafkaCli.Partitions(topic)
		if err != nil {
			info[topic] = 0
		} else {
			info[topic] = len(parts)
		}
	}
	return info
}

// 获取每个topic的每个分区的leader
func getPartsLeader(kafkaCli kafka.Client, topicInfo map[string]int) map[string][]string {
	partLeader := make(map[string][]string)
	for topic, parts := range topicInfo {
		partLeader[topic] = make([]string, 0)
		p := 0
		for p < parts {
			broker, e := kafkaCli.Leader(topic, int32(p))
			if e != nil {
				partLeader[topic] = append(partLeader[topic], "")
			} else {
				partLeader[topic] = append(partLeader[topic], broker.Addr())
			}
			p += 1
			if p > 3 {
				// 只显示前三个
				break
			}
		}
	}
	return partLeader
}

// 获取所有的broker key: addr value id
func getAllBrokerINfo(kafkaCli kafka.Client) map[string]string {
	brkInfo := make(map[string]string, 0)
	brokers := kafkaCli.Brokers()
	for _, b := range brokers {
		brkInfo[b.Addr()] = fmt.Sprintf("%d", b.ID())
	}
	return brkInfo
}

func UIshowCommand(c *cli.Context) error {

	addr := c.String("addr")
	if addr == "" {
		log.Error(`必须传递kafka地址 使用"kakfainfo uishow -h" 查看帮助信息`)
		return errors.New("kafka地址为空")
	}
	// 创建一个kafka客户端
	kafkaCli, err := kafka.NewClient(strings.Split(addr, ","), nil)
	if err != nil {
		log.Error("连接kafka失败: ", err)
		return err
	}

	// 获取所有topic
	topics := getAllTopics(kafkaCli)
	// 获取所有topic对应的分区数量
	topicsinfos := getTopicsInfo(kafkaCli, topics)
	// 获取每个topic的每个分区的leader
	partsLeader := getPartsLeader(kafkaCli, topicsinfos)
	// 获取所有的broker
	brokerInfo := getAllBrokerINfo(kafkaCli)
	defer kafkaCli.Close()
	//// 初始化渲染
	renderInit()

	//// 渲染一个标题
	tx, ty, bx, by := renderPar(0, 0, 0, 0)

	//// 渲染broker信息
	tx, ty, bx, by = renderBrokerInfo(brokerInfo, tx, ty, bx, by)
	////  渲染主题详情
	renderTopicInfo(topicsinfos, partsLeader, tx, ty, bx, by)
	renderLoop()

	return nil
}
<commit_msg>删除不必要的内容<commit_after>package uishow

import (
	"errors"
	"fmt"
	ui "github.com/gizak/termui" // <- ui shortcut, optional
	"github.com/urfave/cli"
	log "github.com/wupeaking/logrus"
	kafka "github.com/wupeaking/sarama"
	"strings"
	"time"
)

var renderCallBacks = make([]func() error, 0)

func registerRenderCallBacks(cb func() error) {
	renderCallBacks = append(renderCallBacks, cb)
}

func renderInit() {
	err := ui.Init()
	if err != nil {
		panic(err)
	}

	// handle key q pressing
	ui.Handle("/sys/kbd/q", func(ui.Event) {
		// press q to quit
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/C-x", func(ui.Event) {
		// handle Ctrl + x combination
	})

	ui.Handle("/sys/kbd", func(ui.Event) {
		// handle all other key pressing
		ui.StopLoop()
		ui.Close()
	})
	ui.Handle("/timer/1s", func(e ui.Event) {
		for _, cb := range renderCallBacks {
			cb()
		}
	})
}

//此块区域的左上角和有下角坐标
func renderPar(tx, ty, bx, by int) (int, int, int, int) {
	//p := ui.NewPar("有边框文本")
	//p.Height = 3
	//p.Width = 50
	//p.TextFgColor = ui.ColorWhite
	////
	//p.BorderLabel = "介绍"
	//p.BorderFg = ui.ColorCyan

	// 再创建一个无边框
	p2 := ui.NewPar("     kafkainfo 是一个简单的调试工具（按任意键退出）")
	p2.Border = false
	p2.X = tx
	p2.Y = by + 1
	p2.Height = 3
	p2.TextFgColor = ui.ColorCyan
	p2.Width = 60

	ui.Render(p2) // feel free to call Render, it's async and non-block

	registerRenderCallBacks(func() error {
		p2.Border = !p2.Border
		p2.BorderLabel = time.Now().String()
		ui.Render(p2)
		return nil
	})

	return p2.X, p2.Y, p2.X + p2.Width, p2.Y + p2.Height

}

// 渲染所有的broker 返回 此块区域的左上角和有下角坐标
func renderBrokerInfo(brokerInfo map[string]string, tx, ty, bx, by int) (int, int, int, int) {
	addrs := make([]string, 0)
	ids := make([]string, 0)

	for addr, id := range brokerInfo {
		addrs = append(addrs, addr)
		ids = append(ids, id)
	}

	addrList := ui.NewList()
	addrList.Items = addrs
	addrList.BorderFg = ui.ColorYellow
	addrList.ItemFgColor = ui.ColorCyan
	addrList.BorderLabel = "kafka broker addr list"
	addrList.Height = len(addrs) + 3
	addrList.Width = 50
	addrList.Y = by + 1
	addrList.X = tx

	idList := ui.NewList()
	idList.Items = ids
	idList.BorderFg = ui.ColorYellow
	idList.ItemFgColor = ui.ColorCyan
	idList.BorderLabel = "kafka broker id list"
	idList.Height = len(addrs) + 3
	idList.Width = 50
	idList.Y = addrList.Y
	idList.X = addrList.X + addrList.Width

	ui.Render(addrList, idList)

	return addrList.X, addrList.Y, addrList.X + addrList.Width, addrList.Y + addrList.Height
}

// 渲染topic信息
// topic名称 | 分区数量 每个分区的leader
func renderTopicInfo(topicinfo map[string]int, partsLeader map[string][]string, tx, ty, bx, by int) (int, int, int, int) {

	infos := make([]string, 0)
	infos = append(infos, "主题 | 分区数量 | 分区leader地址")
	for topic, num := range topicinfo {
		info := fmt.Sprintf("%s | %d | %s", topic, num, strings.Join(partsLeader[topic], " "))
		infos = append(infos, info)
	}
	infoList := ui.NewList()
	infoList.Items = infos
	infoList.BorderFg = ui.ColorYellow
	infoList.ItemFgColor = ui.ColorCyan
	infoList.BorderLabel = "kafka broker topics info list"
	infoList.Height = len(infos) + 3
	infoList.Width = 100
	infoList.Y = by + 1
	infoList.X = tx

	ui.Render(infoList)
	return infoList.X, infoList.Y, infoList.X + infoList.Width, infoList.Y + infoList.Height
}

func renderLoop() {
	ui.Loop()
}

func getAllTopics(kafkaCli kafka.Client) []string {
	topics, err := kafkaCli.Topics()

	if err != nil {
		return nil
	} else {
		return topics
	}
	//brokers := kafkaCli.Brokers()
	//
	//for i, broker := range brokers {
	//	log.WithField("index:", i).Info("id: ", broker.ID, " addr: ", broker.Addr())
	//}
	//return nil
}

// 获取所有的topic对应的分区数量
func getTopicsInfo(kafkaCli kafka.Client, topics []string) map[string]int {
	info := make(map[string]int)
	for _, topic := range topics {
		parts, err := kafkaCli.Partitions(topic)
		if err != nil {
			info[topic] = 0
		} else {
			info[topic] = len(parts)
		}
	}
	return info
}

// 获取每个topic的每个分区的leader
func getPartsLeader(kafkaCli kafka.Client, topicInfo map[string]int) map[string][]string {
	partLeader := make(map[string][]string)
	for topic, parts := range topicInfo {
		partLeader[topic] = make([]string, 0)
		p := 0
		for p < parts {
			broker, e := kafkaCli.Leader(topic, int32(p))
			if e != nil {
				partLeader[topic] = append(partLeader[topic], "")
			} else {
				partLeader[topic] = append(partLeader[topic], broker.Addr())
			}
			p += 1
			if p > 3 {
				// 只显示前三个
				break
			}
		}
	}
	return partLeader
}

// 获取所有的broker key: addr value id
func getAllBrokerINfo(kafkaCli kafka.Client) map[string]string {
	brkInfo := make(map[string]string, 0)
	brokers := kafkaCli.Brokers()
	for _, b := range brokers {
		brkInfo[b.Addr()] = fmt.Sprintf("%d", b.ID())
	}
	return brkInfo
}

func UIshowCommand(c *cli.Context) error {

	addr := c.String("addr")
	if addr == "" {
		log.Error(`必须传递kafka地址 使用"kakfainfo uishow -h" 查看帮助信息`)
		return errors.New("kafka地址为空")
	}
	// 创建一个kafka客户端
	kafkaCli, err := kafka.NewClient(strings.Split(addr, ","), nil)
	if err != nil {
		log.Error("连接kafka失败: ", err)
		return err
	}

	// 获取所有topic
	topics := getAllTopics(kafkaCli)
	// 获取所有topic对应的分区数量
	topicsinfos := getTopicsInfo(kafkaCli, topics)
	// 获取每个topic的每个分区的leader
	partsLeader := getPartsLeader(kafkaCli, topicsinfos)
	// 获取所有的broker
	brokerInfo := getAllBrokerINfo(kafkaCli)
	defer kafkaCli.Close()
	//// 初始化渲染
	renderInit()

	//// 渲染一个标题
	tx, ty, bx, by := renderPar(0, 0, 0, 0)

	//// 渲染broker信息
	tx, ty, bx, by = renderBrokerInfo(brokerInfo, tx, ty, bx, by)
	////  渲染主题详情
	renderTopicInfo(topicsinfos, partsLeader, tx, ty, bx, by)
	renderLoop()

	return nil
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package httpext

import (
	"fmt"
	"net/http"
	"net/http/httputil"

	"github.com/sirupsen/logrus"
)

type httpDebugTransport struct {
	//TODO: get the state and log to its Logger
	originalTransport http.RoundTripper
	httpDebugOption   string
}

// RoundTrip prints passing HTTP requests and received responses
//
// TODO: massively improve this, because the printed information can be wrong:
//  - https://github.com/loadimpact/k6/issues/986
//  - https://github.com/loadimpact/k6/issues/1042
//  - https://github.com/loadimpact/k6/issues/774
func (t httpDebugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	t.debugRequest(req)
	resp, err := t.originalTransport.RoundTrip(req)
	t.debugResponse(resp)
	return resp, err
}

func (t httpDebugTransport) debugRequest(req *http.Request) {
	dump, err := httputil.DumpRequestOut(req, t.httpDebugOption == "full")
	if err != nil {
		logrus.Fatal(err) //TODO: fix...
	}
	fmt.Printf("Request:\n%s\n", dump) //TODO: fix...
}

func (t httpDebugTransport) debugResponse(res *http.Response) {
	if res != nil {
		dump, err := httputil.DumpResponse(res, t.httpDebugOption == "full")
		if err != nil {
			logrus.Fatal(err) //TODO: fix...
		}
		fmt.Printf("Response:\n%s\n", dump) //TODO: fix...
	}
}
<commit_msg>Stop http-debug from exiting on dump error<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package httpext

import (
	"fmt"
	"net/http"
	"net/http/httputil"

	"github.com/sirupsen/logrus"
)

type httpDebugTransport struct {
	//TODO: get the state and log to its Logger
	originalTransport http.RoundTripper
	httpDebugOption   string
}

// RoundTrip prints passing HTTP requests and received responses
//
// TODO: massively improve this, because the printed information can be wrong:
//  - https://github.com/loadimpact/k6/issues/986
//  - https://github.com/loadimpact/k6/issues/1042
//  - https://github.com/loadimpact/k6/issues/774
func (t httpDebugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	t.debugRequest(req)
	resp, err := t.originalTransport.RoundTrip(req)
	t.debugResponse(resp)
	return resp, err
}

func (t httpDebugTransport) debugRequest(req *http.Request) {
	dump, err := httputil.DumpRequestOut(req, t.httpDebugOption == "full")
	if err != nil {
		logrus.Error(err)
	}
	fmt.Printf("Request:\n%s\n", dump) //TODO: fix...
}

func (t httpDebugTransport) debugResponse(res *http.Response) {
	if res != nil {
		dump, err := httputil.DumpResponse(res, t.httpDebugOption == "full")
		if err != nil {
			logrus.Error(err)
		}
		fmt.Printf("Response:\n%s\n", dump) //TODO: fix...
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package fdroidcl

import (
	"bytes"
	"reflect"
	"strings"
	"testing"
	"time"
)

func TestTextDesc(t *testing.T) {
	for _, c := range []struct {
		in   string
		want string
	}{
		{
			"Simple description.",
			"Simple description.",
		},
		{
			"
Simple description.
",
			"Simple description.\n",
		},
		{
			"
Multiple

Paragraphs
",
			"Multiple\n\nParagraphs\n",
		},
		{
			"
Single, very long paragraph that is over 80 characters long and doesn't fit in a single line.
",
			"Single, very long paragraph that is over 80 characters long and doesn't fit in\na single line.\n",
		},
		{
			"
Unordered list:

 Item

 Another item

",
			"Unordered list:\n\n * Item\n * Another item\n",
		},
		{
			"
Link: link title text
",
			"Link: link title[0] text\n\n[0] http://foo.bar\n",
		},
		{
			"
Links: foo1 bar1
",
			"Links: foo1[0] bar1[1]\n\n[0] foo\n[1] bar\n",
		},
	} {
		app := App{Desc: c.in}
		var b bytes.Buffer
		app.TextDesc(&b)
		got := b.String()
		if got != c.want {
			t.Fatalf("Unexpected description.\nGot:\n%s\nWant:\n%s\n",
				got, c.want)
		}
	}
}

func TestLoadIndexXML(t *testing.T) {
	tests := []struct {
		in   string
		want Index
	}{
		{
			`<fdroid>
			<repo name="Foo" version="14"/>
			<application>
				<id>foo.bar</id>
				<name>Foo bar</name>
				<categories>Cat1,Cat2</categories>
				<added>2015-10-02</added>
				<package>
					<version>1.0</version>
					<versioncode>1</versioncode>
					<sig>0123456789abcdef</sig>
				</package>
			</application>
			</fdroid>`,
			Index{
				Repo: Repo{
					Name:    "Foo",
					Version: 14,
				},
				Apps: []App{
					{
						ID:     "foo.bar",
						Name:   "Foo bar",
						Categs: []string{"Cat1", "Cat2"},
						Added:  DateVal{time.Date(2015, 10, 2, 0, 0, 0, 0, time.UTC)},
						Apks: []Apk{
							{
								VName: "1.0",
								VCode: 1,
								Sig:   []byte{1, 35, 69, 103, 137, 171, 205, 239},
							},
						},
					},
				},
			},
		},
	}
	for _, c := range tests {
		r := strings.NewReader(c.in)
		index, err := LoadIndexXML(r)
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		got := *index
		for i := range c.want.Apps {
			app := &c.want.Apps[i]
			for i := range app.Apks {
				apk := &app.Apks[i]
				apk.Repo = &got.Repo
				apk.App = app
			}
		}
		if !reflect.DeepEqual(got, c.want) {
			t.Fatalf("Unexpected index.\nGot:\n%v\nWant:\n%v\n",
				got, c.want)
		}
	}
}
<commit_msg>Fix broken index test<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package fdroidcl

import (
	"bytes"
	"reflect"
	"strings"
	"testing"
	"time"
)

func TestTextDesc(t *testing.T) {
	for _, c := range []struct {
		in   string
		want string
	}{
		{
			"Simple description.",
			"Simple description.",
		},
		{
			"
Simple description.
",
			"Simple description.\n",
		},
		{
			"
Multiple

Paragraphs
",
			"Multiple\n\nParagraphs\n",
		},
		{
			"
Single, very long paragraph that is over 80 characters long and doesn't fit in a single line.
",
			"Single, very long paragraph that is over 80 characters long and doesn't fit in\na single line.\n",
		},
		{
			"
Unordered list:

 Item

 Another item

",
			"Unordered list:\n\n * Item\n * Another item\n",
		},
		{
			"
Link: link title text
",
			"Link: link title[0] text\n\n[0] http://foo.bar\n",
		},
		{
			"
Links: foo1 bar1
",
			"Links: foo1[0] bar1[1]\n\n[0] foo\n[1] bar\n",
		},
	} {
		app := App{Desc: c.in}
		var b bytes.Buffer
		app.TextDesc(&b)
		got := b.String()
		if got != c.want {
			t.Fatalf("Unexpected description.\nGot:\n%s\nWant:\n%s\n",
				got, c.want)
		}
	}
}

func TestLoadIndexXML(t *testing.T) {
	tests := []struct {
		in   string
		want Index
	}{
		{
			`<fdroid>
			<repo name="Foo" version="14"/>
			<application>
				<id>foo.bar</id>
				<name>Foo bar</name>
				<categories>Cat1,Cat2</categories>
				<added>2015-10-02</added>
				<package>
					<version>1.0</version>
					<versioncode>1</versioncode>
					<sig>0123456789abcdef</sig>
				</package>
			</application>
			</fdroid>`,
			Index{
				Repo: Repo{
					Name:    "Foo",
					Version: 14,
				},
				Apps: []App{
					{
						ID:     "foo.bar",
						Name:   "Foo bar",
						Categs: []string{"Cat1", "Cat2"},
						Added:  DateVal{time.Date(2015, 10, 2, 0, 0, 0, 0, time.UTC)},
						Apks: []Apk{
							{
								VName: "1.0",
								VCode: 1,
								Sig:   []byte{1, 35, 69, 103, 137, 171, 205, 239},
							},
						},
					},
				},
			},
		},
	}
	for _, c := range tests {
		r := strings.NewReader(c.in)
		index, err := LoadIndexXML(r)
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		got := *index
		for i := range c.want.Apps {
			app := &c.want.Apps[i]
			for i := range app.Apks {
				apk := &app.Apks[i]
				apk.repo = &got.Repo
				apk.AppID = app.ID
			}
		}
		if !reflect.DeepEqual(got, c.want) {
			t.Fatalf("Unexpected index.\nGot:\n%v\nWant:\n%v\n",
				got, c.want)
		}
	}
}
<|endoftext|>


<commit_before>package logs

import (
	"bufio"
	"encoding/json"
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/pkg/errors"
	"github.com/stitchfix/flotilla-os/config"
	"github.com/stitchfix/flotilla-os/state"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

//
// EKSS3LogsClient corresponds with the aws logs driver
// for ECS and returns logs for runs
//
type EKSS3LogsClient struct {
	logRetentionInDays int64
	logNamespace       string
	s3Client           *s3.S3
	s3Bucket           string
	s3BucketRootDir    string
	logger             *log.Logger
}

type s3Log struct {
	Log    string    `json:"log"`
	Stream string    `json:"stream"`
	Time   time.Time `json:"time"`
}

//
// Name returns the name of the logs client
//
func (lc *EKSS3LogsClient) Name() string {
	return "eks-s3"
}

//
// Initialize sets up the EKSS3LogsClient
//
func (lc *EKSS3LogsClient) Initialize(conf config.Config) error {
	confLogOptions := conf.GetStringMapString("eks.log.driver.options")

	awsRegion := confLogOptions["awslogs-region"]
	if len(awsRegion) == 0 {
		awsRegion = conf.GetString("aws_default_region")
	}

	if len(awsRegion) == 0 {
		return errors.Errorf(
			"EKSS3LogsClient needs one of [eks.log.driver.options.awslogs-region] or [aws_default_region] set in config")
	}

	flotillaMode := conf.GetString("flotilla_mode")
	if flotillaMode != "test" {
		sess := session.Must(session.NewSession(&aws.Config{
			Region: aws.String(awsRegion)}))

		lc.s3Client = s3.New(sess, aws.NewConfig().WithRegion(awsRegion))
	}

	s3BucketName := confLogOptions["s3_bucket_name"]

	if len(s3BucketName) == 0 {
		return errors.Errorf(
			"EKSS3LogsClient needs [eks.log.driver.options.s3_bucket_name] set in config")
	}
	lc.s3Bucket = s3BucketName

	s3BucketRootDir := confLogOptions["s3_bucket_root_dir"]

	if len(s3BucketRootDir) == 0 {
		return errors.Errorf(
			"EKSS3LogsClient needs [eks.log.driver.options.s3_bucket_root_dir] set in config")
	}
	lc.s3BucketRootDir = s3BucketRootDir

	lc.logger = log.New(os.Stderr, "[s3logs] ",
		log.Ldate|log.Ltime|log.Lshortfile)
	return nil
}

func (lc *EKSS3LogsClient) Logs(definition state.Definition, run state.Run, lastSeen *string) (string, *string, error) {
	result, err := lc.getS3Object(run)
	startPosition := int64(0)
	if lastSeen != nil {
		parsed, err := strconv.ParseInt(*lastSeen, 10, 64)
		if err == nil {
			startPosition = parsed
		}
	}

	if result != nil && err == nil {
		acc, position, err := lc.logsToMessageString(result, startPosition)
		newLastSeen := fmt.Sprintf("%d", position)
		return acc, &newLastSeen, err
	}

	return "", nil, errors.Errorf("No logs.")
}

//
// Logs returns all logs from the log stream identified by handle since lastSeen
//
func (lc *EKSS3LogsClient) LogsText(definition state.Definition, run state.Run, w http.ResponseWriter) error {
	result, err := lc.getS3Object(run)

	if result != nil && err == nil {
		return lc.logsToMessage(result, w)
	}

	return nil
}

func (lc *EKSS3LogsClient) getS3Object(run state.Run) (*s3.GetObjectOutput, error) {
	//Pod isn't there yet - dont return a 404
	if run.PodName == nil {
		return nil, errors.New("no pod associated with the run.")
	}
	s3DirName := lc.toS3DirName(run)

	// Get list of S3 objects in the run_id folder.
	result, err := lc.s3Client.ListObjects(&s3.ListObjectsInput{
		Bucket: aws.String(lc.s3Bucket),
		Prefix: aws.String(s3DirName),
	})

	if err != nil {
		return nil, errors.Wrap(err, "problem getting logs")
	}

	if result == nil || result.Contents == nil || len(result.Contents) == 0 {
		return nil, errors.New("no s3 files associated with the run.")
	}

	for _, content := range result.Contents {
		if strings.Contains(*content.Key, *run.PodName) {
			s3Key := content.Key
			result, err := lc.s3Client.GetObject(&s3.GetObjectInput{
				Bucket: aws.String(lc.s3Bucket),
				Key:    aws.String(*s3Key),
			})

			if err != nil {
				return nil, err
			}
			return result, nil
		}
	}
	return nil, errors.New("no s3 files associated with the run.")
}

func (lc *EKSS3LogsClient) toS3DirName(run state.Run) string {
	return fmt.Sprintf("%s/%s", lc.s3BucketRootDir, run.RunID)
}

func (lc *EKSS3LogsClient) logsToMessage(result *s3.GetObjectOutput, w http.ResponseWriter) error {
	reader := bufio.NewReader(result.Body)
	for {
		line, err := reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return err
		} else {
			var parsedLine s3Log
			err := json.Unmarshal(line, &parsedLine)
			if err != nil {
				return err
			}
			_, err = io.WriteString(w, parsedLine.Log)
			if err != nil {
				return err
			}
		}
	}
}

func (lc *EKSS3LogsClient) logsToMessageString(result *s3.GetObjectOutput, startingPosition int64) (string, int64, error) {
	acc := ""
	currentPosition := int64(0)
	// if less than/equal to 0, read entire log.
	if startingPosition <= 0 {
		startingPosition = currentPosition
	}

	// No S3 file or object, return "", 0, err
	if result == nil {
		return acc, startingPosition, errors.New("s3 object not present.")
	}

	reader := bufio.NewReader(result.Body)

	// Reading until startingPosition and discard unneeded lines.
	for currentPosition < startingPosition {
		currentPosition = currentPosition + 1
		_, err := reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return acc, startingPosition, err
		}
	}

	// Read upto MaxLogLines
	for currentPosition <= startingPosition+state.MaxLogLines {
		currentPosition = currentPosition + 1
		line, err := reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return acc, currentPosition, err
		} else {
			var parsedLine s3Log
			err := json.Unmarshal(line, &parsedLine)
			if err == nil {
				acc = fmt.Sprintf("%s%s", acc, parsedLine.Log)
			}
		}
	}

	return acc, currentPosition, nil
}
<commit_msg>fix file handle leak (#243)<commit_after>package logs

import (
	"bufio"
	"encoding/json"
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/pkg/errors"
	"github.com/stitchfix/flotilla-os/config"
	"github.com/stitchfix/flotilla-os/state"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

//
// EKSS3LogsClient corresponds with the aws logs driver
// for ECS and returns logs for runs
//
type EKSS3LogsClient struct {
	logRetentionInDays int64
	logNamespace       string
	s3Client           *s3.S3
	s3Bucket           string
	s3BucketRootDir    string
	logger             *log.Logger
}

type s3Log struct {
	Log    string    `json:"log"`
	Stream string    `json:"stream"`
	Time   time.Time `json:"time"`
}

//
// Name returns the name of the logs client
//
func (lc *EKSS3LogsClient) Name() string {
	return "eks-s3"
}

//
// Initialize sets up the EKSS3LogsClient
//
func (lc *EKSS3LogsClient) Initialize(conf config.Config) error {
	confLogOptions := conf.GetStringMapString("eks.log.driver.options")

	awsRegion := confLogOptions["awslogs-region"]
	if len(awsRegion) == 0 {
		awsRegion = conf.GetString("aws_default_region")
	}

	if len(awsRegion) == 0 {
		return errors.Errorf(
			"EKSS3LogsClient needs one of [eks.log.driver.options.awslogs-region] or [aws_default_region] set in config")
	}

	flotillaMode := conf.GetString("flotilla_mode")
	if flotillaMode != "test" {
		sess := session.Must(session.NewSession(&aws.Config{
			Region: aws.String(awsRegion)}))

		lc.s3Client = s3.New(sess, aws.NewConfig().WithRegion(awsRegion))
	}

	s3BucketName := confLogOptions["s3_bucket_name"]

	if len(s3BucketName) == 0 {
		return errors.Errorf(
			"EKSS3LogsClient needs [eks.log.driver.options.s3_bucket_name] set in config")
	}
	lc.s3Bucket = s3BucketName

	s3BucketRootDir := confLogOptions["s3_bucket_root_dir"]

	if len(s3BucketRootDir) == 0 {
		return errors.Errorf(
			"EKSS3LogsClient needs [eks.log.driver.options.s3_bucket_root_dir] set in config")
	}
	lc.s3BucketRootDir = s3BucketRootDir

	lc.logger = log.New(os.Stderr, "[s3logs] ",
		log.Ldate|log.Ltime|log.Lshortfile)
	return nil
}

func (lc *EKSS3LogsClient) Logs(definition state.Definition, run state.Run, lastSeen *string) (string, *string, error) {
	result, err := lc.getS3Object(run)
	startPosition := int64(0)
	if lastSeen != nil {
		parsed, err := strconv.ParseInt(*lastSeen, 10, 64)
		if err == nil {
			startPosition = parsed
		}
	}

	if result != nil && err == nil {
		acc, position, err := lc.logsToMessageString(result, startPosition)
		newLastSeen := fmt.Sprintf("%d", position)
		return acc, &newLastSeen, err
	}

	return "", nil, errors.Errorf("No logs.")
}

//
// Logs returns all logs from the log stream identified by handle since lastSeen
//
func (lc *EKSS3LogsClient) LogsText(definition state.Definition, run state.Run, w http.ResponseWriter) error {
	result, err := lc.getS3Object(run)

	if result != nil && err == nil {
		return lc.logsToMessage(result, w)
	}

	return nil
}

func (lc *EKSS3LogsClient) getS3Object(run state.Run) (*s3.GetObjectOutput, error) {
	//Pod isn't there yet - dont return a 404
	if run.PodName == nil {
		return nil, errors.New("no pod associated with the run.")
	}
	s3DirName := lc.toS3DirName(run)

	// Get list of S3 objects in the run_id folder.
	result, err := lc.s3Client.ListObjects(&s3.ListObjectsInput{
		Bucket: aws.String(lc.s3Bucket),
		Prefix: aws.String(s3DirName),
	})

	if err != nil {
		return nil, errors.Wrap(err, "problem getting logs")
	}

	if result == nil || result.Contents == nil || len(result.Contents) == 0 {
		return nil, errors.New("no s3 files associated with the run.")
	}

	for _, content := range result.Contents {
		if strings.Contains(*content.Key, *run.PodName) {
			s3Key := content.Key
			result, err := lc.s3Client.GetObject(&s3.GetObjectInput{
				Bucket: aws.String(lc.s3Bucket),
				Key:    aws.String(*s3Key),
			})

			if err != nil {
				return nil, err
			}
			return result, nil
		}
	}
	return nil, errors.New("no s3 files associated with the run.")
}

func (lc *EKSS3LogsClient) toS3DirName(run state.Run) string {
	return fmt.Sprintf("%s/%s", lc.s3BucketRootDir, run.RunID)
}

func (lc *EKSS3LogsClient) logsToMessage(result *s3.GetObjectOutput, w http.ResponseWriter) error {
	reader := bufio.NewReader(result.Body)
	for {
		line, err := reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return err
		} else {
			var parsedLine s3Log
			err := json.Unmarshal(line, &parsedLine)
			if err != nil {
				return err
			}
			_, err = io.WriteString(w, parsedLine.Log)
			if err != nil {
				return err
			}
		}
	}

}

func (lc *EKSS3LogsClient) logsToMessageString(result *s3.GetObjectOutput, startingPosition int64) (string, int64, error) {
	acc := ""
	currentPosition := int64(0)
	// if less than/equal to 0, read entire log.
	if startingPosition <= 0 {
		startingPosition = currentPosition
	}

	// No S3 file or object, return "", 0, err
	if result == nil {
		return acc, startingPosition, errors.New("s3 object not present.")
	}

	reader := bufio.NewReader(result.Body)

	// Reading until startingPosition and discard unneeded lines.
	for currentPosition < startingPosition {
		currentPosition = currentPosition + 1
		_, err := reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return acc, startingPosition, err
		}
	}

	// Read upto MaxLogLines
	for currentPosition <= startingPosition+state.MaxLogLines {
		currentPosition = currentPosition + 1
		line, err := reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return acc, currentPosition, err
		} else {
			var parsedLine s3Log
			err := json.Unmarshal(line, &parsedLine)
			if err == nil {
				acc = fmt.Sprintf("%s%s", acc, parsedLine.Log)
			}
		}
	}

	_ = result.Body.Close()
	return acc, currentPosition, nil
}
<|endoftext|>


<commit_before>package cachet_test

import (
	"fmt"
	"github.com/andygrunwald/cachet"
)

func ExampleTrending_Ping() {
	client, err:= cachet.NewClient("https://demo.cachethq.io/", nil)
	if err != nil {
		panic(err)
	}

	pong, resp, err := client.General.Ping()
	if err != nil {
		panic(err)
	}

	fmt.Printf("Result: %s\n", pong)
	fmt.Printf("Status: %s\n", resp.Status)

	// Output: Result: Pong!
	// Status: 200 OK
}

func ExampleTrending_CreateComponent() {
	client, err:= cachet.NewClient("https://demo.cachethq.io/", nil)
	if err != nil {
		panic(err)
	}

	client.Authentication.SetBasicAuth("test@test.com", "test123")

	component := &cachet.Component{
		Name: "Beer Fridge",
		Description: "Status of the beer fridge in the kitchen",
		Status: cachet.ComponentStatusOperational,
	}
	newComponent, resp, err := client.Components.Create(component)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Result: %s\n", newComponent.Name)
	if newComponent.ID > 0 {
		fmt.Println("ID > 0!")
	}
	fmt.Printf("Status: %s\n", resp.Status)

	// Output: Result: Beer Fridge
	// ID > 0!
	// Status: 200 OK
}<commit_msg>go fmt<commit_after>package cachet_test

import (
	"fmt"
	"github.com/andygrunwald/cachet"
)

func ExampleTrending_Ping() {
	client, err := cachet.NewClient("https://demo.cachethq.io/", nil)
	if err != nil {
		panic(err)
	}

	pong, resp, err := client.General.Ping()
	if err != nil {
		panic(err)
	}

	fmt.Printf("Result: %s\n", pong)
	fmt.Printf("Status: %s\n", resp.Status)

	// Output: Result: Pong!
	// Status: 200 OK
}

func ExampleTrending_CreateComponent() {
	client, err := cachet.NewClient("https://demo.cachethq.io/", nil)
	if err != nil {
		panic(err)
	}

	client.Authentication.SetBasicAuth("test@test.com", "test123")

	component := &cachet.Component{
		Name:        "Beer Fridge",
		Description: "Status of the beer fridge in the kitchen",
		Status:      cachet.ComponentStatusOperational,
	}
	newComponent, resp, err := client.Components.Create(component)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Result: %s\n", newComponent.Name)
	if newComponent.ID > 0 {
		fmt.Println("ID > 0!")
	}
	fmt.Printf("Status: %s\n", resp.Status)

	// Output: Result: Beer Fridge
	// ID > 0!
	// Status: 200 OK
}
<|endoftext|>


<commit_before>package sshexec

import (
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/google/shlex"
	"github.com/pkg/errors"
	"golang.org/x/crypto/ssh"
)

type ExecHandler func([]string, *ssh.ServerConn, io.ReadWriteCloser) error

type Dispatcher struct {
	c *ssh.ServerConfig

	commands map[string]ExecHandler
}

func NewDispatcher(serverKey ssh.Signer, auth Authorizer) *Dispatcher {
	c := &ssh.ServerConfig{
		PublicKeyCallback: auth.Authorize,
		AuthLogCallback: func(conn ssh.ConnMetadata, method string, err error) {
			if err != nil && method != "none" {
				logrus.Infof("Auth error (%s): %s : %+v", method, conn.User(), err)
			}
		},
	}

	c.AddHostKey(serverKey)

	return &Dispatcher{
		c:        c,
		commands: map[string]ExecHandler{},
	}
}

func (d *Dispatcher) Serve(l net.Listener) {
	for {
		conn, err := l.Accept()
		if err != nil {
			logrus.Fatalf("Accept error: %v", err)
		}
		go d.handleConn(conn)
	}
}

func (d *Dispatcher) handleConn(conn net.Conn) {
	sc, ncc, rc, err := ssh.NewServerConn(conn, d.c)
	if err != nil {
		logrus.Infof("Server conn error: %s", err)
		return
	}
	defer sc.Close()

	go ssh.DiscardRequests(rc)

	nc := <-ncc

	if nc.ChannelType() != "session" {
		if err := nc.Reject(ssh.UnknownChannelType, "channel not currently supported"); err != nil {
			logrus.Errorf("Reject error: %v", err)
		}
		return
	}
	c, rc, err := nc.Accept()
	if err != nil {
		logrus.Errorf("Accept error: %v", err)
		return
	}

	hchan := make(chan func(*ssh.ServerConn, io.ReadWriteCloser) error)

	go func() {
		for r := range rc {
			var err error
			var h func(*ssh.ServerConn, io.ReadWriteCloser) error

			switch r.Type {
			case "env":
				// TODO: Print envs
			case "shell":
				err = errors.New("shell not supported")
			case "exec":
				l := binary.BigEndian.Uint32(r.Payload)
				args, err := shlex.Split(string(r.Payload[4 : l+4]))
				if err != nil {
					err = errors.Wrap(err, "unable to parse exec command")
				} else if len(args) == 0 {
					err = errors.New("no exec command given")
				} else {
					var f ExecHandler
					f, err = d.lookupCommand(args[0])
					if err == nil {
						h = func(sc *ssh.ServerConn, rw io.ReadWriteCloser) error {
							return f(args, sc, rw)
						}
					}
				}
			default:
				err = errors.Errorf("unknown type: %s", r.Type)
			}

			if r.WantReply {
				if err != nil {
					errMsg := err.Error()
					logrus.Errorf("Request rejected: %+v", errMsg)
					payload := make([]byte, len(errMsg)+4)
					binary.BigEndian.PutUint32(payload, uint32(len(errMsg)))
					copy(payload[4:], errMsg)
					err = r.Reply(false, payload)
				} else {
					err = r.Reply(true, nil)
				}
				if err != nil {
					logrus.Infof("Reply error: %+v", err)
					continue
				}
			} else if err != nil {
				logrus.Infof("Request error: %+v", err)
				continue
			}
			if h != nil {
				hchan <- h
			}

		}
	}()

	h := <-hchan

	var responseStatus uint32
	err = h(sc, c)
	if err != nil {
		logrus.Debugf("Command failed: %+v", err)

		fmt.Fprintf(c.Stderr(), "Command failed: %v\n", err)

		// TODO: Check if has status function
		responseStatus = 1
	}

	payload := make([]byte, 4)
	binary.BigEndian.PutUint32(payload, responseStatus)
	if _, err := c.SendRequest("exit-status", false, payload); err != nil {
		logrus.Errorf("Send exit status error: %v", err)
	}

	c.Close()

	// Before closing the connection give client a chance to clean up
	time.Sleep(time.Second)
}

func (d *Dispatcher) AddCommand(name string, h ExecHandler) {
	d.commands[name] = h
}

func (d *Dispatcher) lookupCommand(name string) (ExecHandler, error) {
	cmd, ok := d.commands[name]
	if !ok {
		return nil, errors.Errorf("command %q does not exist", name)
	}

	return cmd, nil
}
<commit_msg>Support handling multiple sessions per connection<commit_after>package sshexec

import (
	"encoding/binary"
	"fmt"
	"io"
	"net"

	"github.com/Sirupsen/logrus"
	"github.com/google/shlex"
	"github.com/pkg/errors"
	"golang.org/x/crypto/ssh"
)

type ExecHandler func([]string, *ssh.ServerConn, io.ReadWriteCloser) error

type Dispatcher struct {
	c *ssh.ServerConfig

	commands map[string]ExecHandler
}

func NewDispatcher(serverKey ssh.Signer, auth Authorizer) *Dispatcher {
	c := &ssh.ServerConfig{
		PublicKeyCallback: auth.Authorize,
		AuthLogCallback: func(conn ssh.ConnMetadata, method string, err error) {
			if err != nil && method != "none" {
				logrus.Infof("Auth error (%s): %s : %+v", method, conn.User(), err)
			}
		},
	}

	c.AddHostKey(serverKey)

	return &Dispatcher{
		c:        c,
		commands: map[string]ExecHandler{},
	}
}

func (d *Dispatcher) Serve(l net.Listener) {
	for {
		conn, err := l.Accept()
		if err != nil {
			logrus.Fatalf("Accept error: %v", err)
		}
		go d.handleConn(conn)
	}
}

func (d *Dispatcher) handleConn(conn net.Conn) {
	sc, ncc, rc, err := ssh.NewServerConn(conn, d.c)
	if err != nil {
		logrus.Infof("Server conn error: %s", err)
		return
	}
	defer sc.Close()

	go ssh.DiscardRequests(rc)

	for nc := range ncc {
		if nc.ChannelType() != "session" {
			logrus.Debugf("Rejecting channel type: %s", nc.ChannelType())
			if err := nc.Reject(ssh.UnknownChannelType, "channel not currently supported"); err != nil {
				logrus.Errorf("Reject error: %v", err)
			}
			continue
		}

		c, rc, err := nc.Accept()
		if err != nil {
			logrus.Errorf("Accept error: %v", err)
			break
		}

		hchan := make(chan execHandler)

		go d.handleChannelRequests(rc, hchan)

		go func() {
			defer c.Close()

			h := <-hchan

			var responseStatus uint32
			err = h(sc, closeChannelWriter{c})
			if err != nil {
				logrus.Debugf("Command failed: %+v", err)

				fmt.Fprintf(c.Stderr(), "Command failed: %v\n", err)

				// TODO: Check if has status function
				responseStatus = 1
			}

			payload := make([]byte, 4)
			binary.BigEndian.PutUint32(payload, responseStatus)
			if _, err := c.SendRequest("exit-status", false, payload); err != nil {
				logrus.Errorf("Send exit status error: %v", err)
			}
		}()
	}
}

type execHandler func(*ssh.ServerConn, io.ReadWriteCloser) error

func (d *Dispatcher) handleChannelRequests(rc <-chan *ssh.Request, hchan chan<- execHandler) {
	for r := range rc {
		var err error
		var h func(*ssh.ServerConn, io.ReadWriteCloser) error

		switch r.Type {
		case "env":
			// TODO: Print envs
		case "shell":
			err = errors.New("shell not supported")
		case "exec":
			l := binary.BigEndian.Uint32(r.Payload)
			args, err := shlex.Split(string(r.Payload[4 : l+4]))
			if err != nil {
				err = errors.Wrap(err, "unable to parse exec command")
			} else if len(args) == 0 {
				err = errors.New("no exec command given")
			} else {
				var f ExecHandler
				f, err = d.lookupCommand(args[0])
				if err == nil {
					h = func(sc *ssh.ServerConn, rw io.ReadWriteCloser) error {
						return f(args, sc, rw)
					}
				}
			}
		default:
			err = errors.Errorf("unknown type: %s", r.Type)
		}

		if r.WantReply {
			if err != nil {
				errMsg := err.Error()
				logrus.Errorf("Request rejected: %+v", errMsg)
				payload := make([]byte, len(errMsg)+4)
				binary.BigEndian.PutUint32(payload, uint32(len(errMsg)))
				copy(payload[4:], errMsg)
				err = r.Reply(false, payload)
			} else {
				err = r.Reply(true, nil)
			}
			if err != nil {
				logrus.Infof("Reply error: %+v", err)
				continue
			}
		} else if err != nil {
			logrus.Infof("Request error: %+v", err)
			continue
		}
		if h != nil {
			hchan <- h
		}

	}
}

func (d *Dispatcher) AddCommand(name string, h ExecHandler) {
	d.commands[name] = h
}

func (d *Dispatcher) lookupCommand(name string) (ExecHandler, error) {
	cmd, ok := d.commands[name]
	if !ok {
		return nil, errors.Errorf("command %q does not exist", name)
	}

	return cmd, nil
}

type closeChannelWriter struct {
	ssh.Channel
}

func (c closeChannelWriter) Close() error {
	return c.CloseWrite()
}
<|endoftext|>


<commit_before>package querybuilder

import "database/sql"

type DBTable interface {
	DB
	DB() *sql.DB
	TableName() string
	Alias() string
	Driver() string
}

type Table struct {
	DBTable
}

//QueryBuilder return querybuilder of  table
func (t *Table) QueryBuilder() *Builder {
	return &Builder{
		Driver: t.DBTable.Driver(),
	}
}

//FieldAlias return field name with table alias.
func (t *Table) FieldAlias(field string) string {
	return t.Alias() + "." + field
}

//NewSelect : create  select query for table
func (t *Table) NewSelect() *Select {
	Select := t.QueryBuilder().NewSelect()
	Select.From.Add(t.TableName())
	return Select
}

//NewInsert : new insert query for table node
func (t *Table) NewInsert() *Insert {
	Insert := t.QueryBuilder().NewInsert(t.TableName())
	return Insert

}

//NewUpdate : new update query for table
func (t *Table) NewUpdate() *Update {
	Update := t.QueryBuilder().NewUpdate(t.TableName())
	Update.Update.SetAlias(t.TableName())
	return Update
}

//NewDelete : build delete query for table node
func (t *Table) NewDelete() *Delete {
	Delete := t.QueryBuilder().NewDelete(t.TableName())
	Delete.Delete.SetAlias(t.TableName())
	return Delete
}

//BuildCount : build count select query for table
func (t *Table) BuildCount() *Select {
	Select := t.NewSelect()
	Select.Select.Add("count(*)")
	return Select
}

//Count : count  from table  by given select t.QueryBuilder().
func (t *Table) Count(Select *Select) (int, error) {
	var result int
	row := Select.QueryRow(t)
	err := row.Scan(&result)
	if err != nil {
		return 0, err
	}
	return result, nil
}

func NewTable(dbtable DBTable) *Table {
	return &Table{
		DBTable: dbtable,
	}
}
<commit_msg>update<commit_after>package querybuilder

import "database/sql"

type DBTable interface {
	DB
	DB() *sql.DB
	TableName() string
	Alias() string
	Driver() string
}

type Table struct {
	DBTable
}

//QueryBuilder return querybuilder of  table
func (t *Table) QueryBuilder() *Builder {
	return &Builder{
		Driver: t.DBTable.Driver(),
	}
}

//FieldAlias return field name with table alias.
func (t *Table) FieldAlias(field string) string {
	return t.Alias() + "." + field
}

//NewSelect : create  select query for table
func (t *Table) NewSelect() *Select {
	Select := t.QueryBuilder().NewSelect()
	Select.From.Add(t.TableName())
	return Select
}

//NewInsert : new insert query for table node
func (t *Table) NewInsert() *Insert {
	Insert := t.QueryBuilder().NewInsert(t.TableName())
	return Insert

}

//NewUpdate : new update query for table
func (t *Table) NewUpdate() *Update {
	Update := t.QueryBuilder().NewUpdate(t.TableName())
	return Update
}

//NewDelete : build delete query for table node
func (t *Table) NewDelete() *Delete {
	Delete := t.QueryBuilder().NewDelete(t.TableName())
	Delete.Delete.SetAlias(t.TableName())
	return Delete
}

//BuildCount : build count select query for table
func (t *Table) BuildCount() *Select {
	Select := t.NewSelect()
	Select.Select.Add("count(*)")
	return Select
}

//Count : count  from table  by given select t.QueryBuilder().
func (t *Table) Count(Select *Select) (int, error) {
	var result int
	row := Select.QueryRow(t)
	err := row.Scan(&result)
	if err != nil {
		return 0, err
	}
	return result, nil
}

func NewTable(dbtable DBTable) *Table {
	return &Table{
		DBTable: dbtable,
	}
}
<|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.

// +build example jsgo

package main

import (
	"bytes"
	"fmt"
	"image"
	_ "image/jpeg"
	"log"
	"math"

	"github.com/gopherjs/gopherjs/js"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/ebitenutil"
	"github.com/hajimehoshi/ebiten/examples/resources/images"
	"github.com/hajimehoshi/ebiten/inpututil"
)

const (
	screenWidth  = 320
	screenHeight = 240
)

var (
	count        = 0
	gophersImage *ebiten.Image
	extraImages  []*ebiten.Image
)

func update(screen *ebiten.Image) error {
	if inpututil.IsKeyJustPressed(ebiten.KeySpace) && js.Global != nil {
		doc := js.Global.Get("document")
		canvas := doc.Call("getElementsByTagName", "canvas").Index(0)
		context := canvas.Call("getContext", "webgl")
		context.Call("getExtension", "WEBGL_lose_context").Call("loseContext")
		fmt.Println("Context Lost!")
		return nil
	}

	count++
	if ebiten.IsRunningSlowly() {
		return nil
	}
	w, h := gophersImage.Size()
	op := &ebiten.DrawImageOptions{}

	// For the details, see examples/rotate.
	op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
	op.GeoM.Rotate(float64(count%360) * 2 * math.Pi / 360)
	op.GeoM.Translate(screenWidth/2, screenHeight/2)
	screen.DrawImage(gophersImage, op)

	ebitenutil.DebugPrint(screen, "Press Space to force GL context lost!\n(Browser only)")

	return nil
}

func main() {
	// Decode image from a byte slice instead of a file so that
	// this example works in any working directory.
	// If you want to use a file, there are some options:
	// 1) Use os.Open and pass the file to the image decoder.
	//    This is a very regular way, but doesn't work on browsers.
	// 2) Use ebitenutil.OpenFile and pass the file to the image decoder.
	//    This works even on browsers.
	// 3) Use ebitenutil.NewImageFromFile to create an ebiten.Image directly from a file.
	//    This also works on browsers.
	img, _, err := image.Decode(bytes.NewReader(images.Gophers_jpg))
	if err != nil {
		log.Fatal(err)
	}
	gophersImage, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault)

	// Extend the shared backend GL texture on purpose.
	for i := 0; i < 20; i++ {
		eimg, _ := ebiten.NewImageFromImage(img, ebiten.FilterDefault)
		extraImages = append(extraImages, eimg)
	}

	if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Context Lost (Ebiten Demo)"); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>examples/contextlost: Bug fix: don't crash on Edge<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.

// +build example jsgo

package main

import (
	"bytes"
	"fmt"
	"image"
	_ "image/jpeg"
	"log"
	"math"

	"github.com/gopherjs/gopherjs/js"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/ebitenutil"
	"github.com/hajimehoshi/ebiten/examples/resources/images"
	"github.com/hajimehoshi/ebiten/inpututil"
)

const (
	screenWidth  = 320
	screenHeight = 240
)

var (
	count        = 0
	gophersImage *ebiten.Image
	extraImages  []*ebiten.Image
)

func update(screen *ebiten.Image) error {
	if inpututil.IsKeyJustPressed(ebiten.KeySpace) && js.Global != nil {
		doc := js.Global.Get("document")
		canvas := doc.Call("getElementsByTagName", "canvas").Index(0)
		context := canvas.Call("getContext", "webgl")
		if context == nil {
			context = canvas.Call("getContext", "experimental-webgl")
		}
		// Edge might not support the extension. See
		// https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_lose_context
		if ext := context.Call("getExtension", "WEBGL_lose_context"); ext != nil {
			ext.Call("loseContext")
			fmt.Println("Context Lost!")
		} else {
			fmt.Println("Fail to force context lost. Edge might not support the extension yet.")
		}
		return nil
	}

	count++
	if ebiten.IsRunningSlowly() {
		return nil
	}
	w, h := gophersImage.Size()
	op := &ebiten.DrawImageOptions{}

	// For the details, see examples/rotate.
	op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
	op.GeoM.Rotate(float64(count%360) * 2 * math.Pi / 360)
	op.GeoM.Translate(screenWidth/2, screenHeight/2)
	screen.DrawImage(gophersImage, op)

	ebitenutil.DebugPrint(screen, "Press Space to force GL context lost!\n(Browser only)")

	return nil
}

func main() {
	// Decode image from a byte slice instead of a file so that
	// this example works in any working directory.
	// If you want to use a file, there are some options:
	// 1) Use os.Open and pass the file to the image decoder.
	//    This is a very regular way, but doesn't work on browsers.
	// 2) Use ebitenutil.OpenFile and pass the file to the image decoder.
	//    This works even on browsers.
	// 3) Use ebitenutil.NewImageFromFile to create an ebiten.Image directly from a file.
	//    This also works on browsers.
	img, _, err := image.Decode(bytes.NewReader(images.Gophers_jpg))
	if err != nil {
		log.Fatal(err)
	}
	gophersImage, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault)

	// Extend the shared backend GL texture on purpose.
	for i := 0; i < 20; i++ {
		eimg, _ := ebiten.NewImageFromImage(img, ebiten.FilterDefault)
		extraImages = append(extraImages, eimg)
	}

	if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Context Lost (Ebiten Demo)"); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 e2e

import (
	"fmt"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/util/json"
	"k8s.io/kubernetes/test/e2e/framework"

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

var _ = framework.KubeDescribe("Cluster level logging using GCL [Flaky]", func() {
	f := framework.NewDefaultFramework("gcl-logging")

	BeforeEach(func() {
		// TODO (crassirostris): Expand to GKE once the test is stable
		framework.SkipUnlessProviderIs("gce")
	})

	It("should check that logs from containers are ingested in GCL", func() {
		By("Running synthetic logger")
		createSynthLogger(f, expectedLinesCount)
		defer f.PodClient().Delete(synthLoggerPodName, &api.DeleteOptions{})
		err := framework.WaitForPodSuccessInNamespace(f.ClientSet, synthLoggerPodName, f.Namespace.Name)
		framework.ExpectNoError(err, fmt.Sprintf("Should've successfully waited for pod %s to succeed", synthLoggerPodName))

		By("Waiting for logs to ingest")
		totalMissing := expectedLinesCount
		for start := time.Now(); time.Since(start) < ingestionTimeout; time.Sleep(ingestionRetryDelay) {
			var err error
			totalMissing, err = getMissingLinesCountGcl(f, synthLoggerPodName, expectedLinesCount)
			if err != nil {
				framework.Logf("Failed to get missing lines count due to %v", err)
				totalMissing = expectedLinesCount
			} else if totalMissing > 0 {
				framework.Logf("Still missing %d lines", totalMissing)
			}

			if totalMissing == 0 {
				break
			}
		}

		if totalMissing > 0 {
			if err := reportLogsFromFluentdPod(f); err != nil {
				framework.Logf("Failed to report logs from fluentd pod due to %v", err)
			}
		}

		Expect(totalMissing).To(Equal(0), "Some log lines are still missing")
	})
})

func getMissingLinesCountGcl(f *framework.Framework, podName string, expectedCount int) (int, error) {
	gclFilter := fmt.Sprintf("resource.labels.pod_id:%s AND resource.labels.namespace_id:%s", podName, f.Namespace.Name)
	entries, err := readFilteredEntriesFromGcl(gclFilter)
	if err != nil {
		return 0, err
	}

	occurrences := make(map[int]int)
	for _, entry := range entries {
		lineNumber, err := strconv.Atoi(strings.TrimSpace(entry))
		if err != nil {
			continue
		}
		if lineNumber < 0 || lineNumber >= expectedCount {
			framework.Logf("Unexpected line number: %d", lineNumber)
		} else {
			// Duplicates are possible and fine, fluentd has at-least-once delivery
			occurrences[lineNumber]++
		}
	}

	return expectedCount - len(occurrences), nil
}

type LogEntry struct {
	TextPayload string
}

// Since GCL API is not easily available from the outside of cluster
// we use gcloud command to perform search with filter
func readFilteredEntriesFromGcl(filter string) ([]string, error) {
	framework.Logf("Reading entries from GCL with filter '%v'", filter)
	argList := []string{"beta",
		"logging",
		"read",
		filter,
		"--format",
		"json",
		"--project",
		framework.TestContext.CloudConfig.ProjectID,
	}
	output, err := exec.Command("gcloud", argList...).CombinedOutput()
	if err != nil {
		return nil, err
	}

	var entries []*LogEntry
	if err = json.Unmarshal(output, &entries); err != nil {
		return nil, err
	}
	framework.Logf("Read %d entries from GCL", len(entries))

	var result []string
	for _, entry := range entries {
		if entry.TextPayload != "" {
			result = append(result, entry.TextPayload)
		}
	}

	return result, nil
}
<commit_msg>Promoted GCL e2e test to stable<commit_after>/*
Copyright 2016 The Kubernetes 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 e2e

import (
	"fmt"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/util/json"
	"k8s.io/kubernetes/test/e2e/framework"

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

var _ = framework.KubeDescribe("Cluster level logging using GCL", func() {
	f := framework.NewDefaultFramework("gcl-logging")

	BeforeEach(func() {
		// TODO (crassirostris): Expand to GKE once the test is stable
		framework.SkipUnlessProviderIs("gce")
	})

	It("should check that logs from containers are ingested in GCL", func() {
		By("Running synthetic logger")
		createSynthLogger(f, expectedLinesCount)
		defer f.PodClient().Delete(synthLoggerPodName, &api.DeleteOptions{})
		err := framework.WaitForPodSuccessInNamespace(f.ClientSet, synthLoggerPodName, f.Namespace.Name)
		framework.ExpectNoError(err, fmt.Sprintf("Should've successfully waited for pod %s to succeed", synthLoggerPodName))

		By("Waiting for logs to ingest")
		totalMissing := expectedLinesCount
		for start := time.Now(); time.Since(start) < ingestionTimeout; time.Sleep(ingestionRetryDelay) {
			var err error
			totalMissing, err = getMissingLinesCountGcl(f, synthLoggerPodName, expectedLinesCount)
			if err != nil {
				framework.Logf("Failed to get missing lines count due to %v", err)
				totalMissing = expectedLinesCount
			} else if totalMissing > 0 {
				framework.Logf("Still missing %d lines", totalMissing)
			}

			if totalMissing == 0 {
				break
			}
		}

		if totalMissing > 0 {
			if err := reportLogsFromFluentdPod(f); err != nil {
				framework.Logf("Failed to report logs from fluentd pod due to %v", err)
			}
		}

		Expect(totalMissing).To(Equal(0), "Some log lines are still missing")
	})
})

func getMissingLinesCountGcl(f *framework.Framework, podName string, expectedCount int) (int, error) {
	gclFilter := fmt.Sprintf("resource.labels.pod_id:%s AND resource.labels.namespace_id:%s", podName, f.Namespace.Name)
	entries, err := readFilteredEntriesFromGcl(gclFilter)
	if err != nil {
		return 0, err
	}

	occurrences := make(map[int]int)
	for _, entry := range entries {
		lineNumber, err := strconv.Atoi(strings.TrimSpace(entry))
		if err != nil {
			continue
		}
		if lineNumber < 0 || lineNumber >= expectedCount {
			framework.Logf("Unexpected line number: %d", lineNumber)
		} else {
			// Duplicates are possible and fine, fluentd has at-least-once delivery
			occurrences[lineNumber]++
		}
	}

	return expectedCount - len(occurrences), nil
}

type LogEntry struct {
	TextPayload string
}

// Since GCL API is not easily available from the outside of cluster
// we use gcloud command to perform search with filter
func readFilteredEntriesFromGcl(filter string) ([]string, error) {
	framework.Logf("Reading entries from GCL with filter '%v'", filter)
	argList := []string{"beta",
		"logging",
		"read",
		filter,
		"--format",
		"json",
		"--project",
		framework.TestContext.CloudConfig.ProjectID,
	}
	output, err := exec.Command("gcloud", argList...).CombinedOutput()
	if err != nil {
		return nil, err
	}

	var entries []*LogEntry
	if err = json.Unmarshal(output, &entries); err != nil {
		return nil, err
	}
	framework.Logf("Read %d entries from GCL", len(entries))

	var result []string
	for _, entry := range entries {
		if entry.TextPayload != "" {
			result = append(result, entry.TextPayload)
		}
	}

	return result, nil
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

type Event struct {
	Name string
	Link string
	Desc string
}

func main() {
	nameMap := map[string]string{
		"afterprint":               "AfterPrint",
		"animationend":             "AnimationEnd",
		"animationiteration":       "AnimationIteration",
		"animationstart":           "AnimationStart",
		"audioprocess":             "AudioProcess",
		"audioend":                 "AudioEnd",
		"audiostart":               "AudioStart",
		"beforeprint":              "BeforePrint",
		"beforeunload":             "BeforeUnload",
		"canplay":                  "CanPlay",
		"canplaythrough":           "CanPlayThrough",
		"chargingchange":           "ChargingChange",
		"chargingtimechange":       "ChargingTimeChange",
		"compassneedscalibration":  "CompassNeedsCalibration",
		"compositionend":           "CompositionEnd",
		"compositionstart":         "CompositionStart",
		"compositionupdate":        "CompositionUpdate",
		"contextmenu":              "ContextMenu",
		"dblclick":                 "DoubleClick",
		"devicelight":              "DeviceLight",
		"devicemotion":             "DeviceMotion",
		"deviceorientation":        "DeviceOrientation",
		"deviceproximity":          "DeviceProximity",
		"dischargingtimechange":    "DischargingTimeChange",
		"dragend":                  "DragEnd",
		"dragenter":                "DragEnter",
		"dragleave":                "DragLeave",
		"dragover":                 "DragOver",
		"dragstart":                "DragStart",
		"durationchange":           "DurationChange",
		"focusin":                  "FocusIn",
		"focusout":                 "FocusOut",
		"fullscreenchange":         "FullScreenChange",
		"fullscreenerror":          "FullScreenError",
		"gamepadconnected":         "GamepadConnected",
		"gamepaddisconnected":      "GamepadDisconnected",
		"gotpointercapture":        "GotPointerCapture",
		"hashchange":               "HashChange",
		"keydown":                  "KeyDown",
		"keypress":                 "KeyPress",
		"keyup":                    "KeyUp",
		"languagechange":           "LanguageChange",
		"levelchange":              "LevelChange",
		"loadeddata":               "LoadedData",
		"loadedmetadata":           "LoadedMetadata",
		"loadend":                  "LoadEnd",
		"loadstart":                "LoadStart",
		"lostpointercapture":       "LostPointerCapture",
		"mousedown":                "MouseDown",
		"mouseenter":               "MouseEnter",
		"mouseleave":               "MouseLeave",
		"mousemove":                "MouseMove",
		"mouseout":                 "MouseOut",
		"mouseover":                "MouseOver",
		"mouseup":                  "MouseUp",
		"noupdate":                 "NoUpdate",
		"nomatch":                  "NoMatch",
		"notificationclick":        "NotificationClick",
		"orientationchange":        "OrientationChange",
		"pagehide":                 "PageHide",
		"pageshow":                 "PageShow",
		"pointercancel":            "PointerCancel",
		"pointerdown":              "PointerDown",
		"pointerenter":             "PointerEnter",
		"pointerleave":             "PointerLeave",
		"pointerlockchange":        "PointerLockChange",
		"pointerlockerror":         "PointerLockError",
		"pointermove":              "PointerMove",
		"pointerout":               "PointerOut",
		"pointerover":              "PointerOver",
		"pointerup":                "PointerUp",
		"popstate":                 "PopState",
		"pushsubscriptionchange":   "PushSubscriptionChange",
		"ratechange":               "RateChange",
		"readystatechange":         "ReadyStateChange",
		"resourcetimingbufferfull": "ResourceTimingBufferFull",
		"selectstart":              "SelectStart",
		"selectionchange":          "SelectionChange",
		"soundend":                 "SoundEnd",
		"soundstart":               "SoundStart",
		"speechend":                "SpeechEnd",
		"speechstart":              "SpeechStart",
		"timeupdate":               "TimeUpdate",
		"touchcancel":              "TouchCancel",
		"touchend":                 "TouchEnd",
		"touchenter":               "TouchEnter",
		"touchleave":               "TouchLeave",
		"touchmove":                "TouchMove",
		"touchstart":               "TouchStart",
		"transitionend":            "TransitionEnd",
		"updateready":              "UpdateReady",
		"upgradeneeded":            "UpgradeNeeded",
		"userproximity":            "UserProximity",
		"versionchange":            "VersionChange",
		"visibilitychange":         "VisibilityChange",
		"voiceschanged":            "VoicesChanged",
		"volumechange":             "VolumeChange",
		"vrdisplayconnected":       "VRDisplayConnected",
		"vrdisplaydisconnected":    "VRDisplayDisconnected",
		"vrdisplaypresentchange":   "VRDisplayPresentChange",
	}

	doc, err := goquery.NewDocument("https://developer.mozilla.org/en-US/docs/Web/Events")
	if err != nil {
		panic(err)
	}

	events := make(map[string]*Event)

	doc.Find(".standard-table").Eq(0).Find("tr").Each(func(i int, s *goquery.Selection) {
		cols := s.Find("td")
		if cols.Length() == 0 || cols.Find(".icon-thumbs-down-alt").Length() != 0 {
			return
		}
		link := cols.Eq(0).Find("a").Eq(0)
		var e Event
		e.Name = link.Text()
		e.Link, _ = link.Attr("href")
		e.Desc = strings.TrimSpace(cols.Eq(3).Text())
		if e.Desc == "" {
			e.Desc = "(no documentation)"
		}

		funName := nameMap[e.Name]
		if funName == "" {
			funName = capitalize(e.Name)
		}

		events[funName] = &e
	})

	var names []string
	for name := range events {
		names = append(names, name)
	}
	sort.Strings(names)

	file, err := os.Create("event.gen.go")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	fmt.Fprint(file, `//go:generate go run generate.go

// Package event defines markup to bind DOM events.
//
// Generated from "Event reference" by Mozilla Contributors, https://developer.mozilla.org/en-US/docs/Web/Events, licensed under CC-BY-SA 2.5.
package event

import "github.com/gopherjs/vecty"
`)

	for _, name := range names {
		e := events[name]
		fmt.Fprintf(file, `
// %s
//
// https://developer.mozilla.org%s
func %s(listener func(*vecty.Event)) *vecty.EventListener {
	return &vecty.EventListener{Name: "%s", Listener: listener}
}
`, e.Desc, e.Link[6:], name, e.Name)
	}
}

func capitalize(s string) string {
	return strings.ToUpper(s[:1]) + s[1:]
}
<commit_msg>event: keep comments at readable lengths<commit_after>// +build ignore

package main

import (
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

type Event struct {
	Name string
	Link string
	Desc string
	Spec string
}

func main() {
	nameMap := map[string]string{
		"afterprint":               "AfterPrint",
		"animationend":             "AnimationEnd",
		"animationiteration":       "AnimationIteration",
		"animationstart":           "AnimationStart",
		"audioprocess":             "AudioProcess",
		"audioend":                 "AudioEnd",
		"audiostart":               "AudioStart",
		"beforeprint":              "BeforePrint",
		"beforeunload":             "BeforeUnload",
		"canplay":                  "CanPlay",
		"canplaythrough":           "CanPlayThrough",
		"chargingchange":           "ChargingChange",
		"chargingtimechange":       "ChargingTimeChange",
		"compassneedscalibration":  "CompassNeedsCalibration",
		"compositionend":           "CompositionEnd",
		"compositionstart":         "CompositionStart",
		"compositionupdate":        "CompositionUpdate",
		"contextmenu":              "ContextMenu",
		"dblclick":                 "DoubleClick",
		"devicelight":              "DeviceLight",
		"devicemotion":             "DeviceMotion",
		"deviceorientation":        "DeviceOrientation",
		"deviceproximity":          "DeviceProximity",
		"dischargingtimechange":    "DischargingTimeChange",
		"dragend":                  "DragEnd",
		"dragenter":                "DragEnter",
		"dragleave":                "DragLeave",
		"dragover":                 "DragOver",
		"dragstart":                "DragStart",
		"durationchange":           "DurationChange",
		"focusin":                  "FocusIn",
		"focusout":                 "FocusOut",
		"fullscreenchange":         "FullScreenChange",
		"fullscreenerror":          "FullScreenError",
		"gamepadconnected":         "GamepadConnected",
		"gamepaddisconnected":      "GamepadDisconnected",
		"gotpointercapture":        "GotPointerCapture",
		"hashchange":               "HashChange",
		"keydown":                  "KeyDown",
		"keypress":                 "KeyPress",
		"keyup":                    "KeyUp",
		"languagechange":           "LanguageChange",
		"levelchange":              "LevelChange",
		"loadeddata":               "LoadedData",
		"loadedmetadata":           "LoadedMetadata",
		"loadend":                  "LoadEnd",
		"loadstart":                "LoadStart",
		"lostpointercapture":       "LostPointerCapture",
		"mousedown":                "MouseDown",
		"mouseenter":               "MouseEnter",
		"mouseleave":               "MouseLeave",
		"mousemove":                "MouseMove",
		"mouseout":                 "MouseOut",
		"mouseover":                "MouseOver",
		"mouseup":                  "MouseUp",
		"noupdate":                 "NoUpdate",
		"nomatch":                  "NoMatch",
		"notificationclick":        "NotificationClick",
		"orientationchange":        "OrientationChange",
		"pagehide":                 "PageHide",
		"pageshow":                 "PageShow",
		"pointercancel":            "PointerCancel",
		"pointerdown":              "PointerDown",
		"pointerenter":             "PointerEnter",
		"pointerleave":             "PointerLeave",
		"pointerlockchange":        "PointerLockChange",
		"pointerlockerror":         "PointerLockError",
		"pointermove":              "PointerMove",
		"pointerout":               "PointerOut",
		"pointerover":              "PointerOver",
		"pointerup":                "PointerUp",
		"popstate":                 "PopState",
		"pushsubscriptionchange":   "PushSubscriptionChange",
		"ratechange":               "RateChange",
		"readystatechange":         "ReadyStateChange",
		"resourcetimingbufferfull": "ResourceTimingBufferFull",
		"selectstart":              "SelectStart",
		"selectionchange":          "SelectionChange",
		"soundend":                 "SoundEnd",
		"soundstart":               "SoundStart",
		"speechend":                "SpeechEnd",
		"speechstart":              "SpeechStart",
		"timeupdate":               "TimeUpdate",
		"touchcancel":              "TouchCancel",
		"touchend":                 "TouchEnd",
		"touchenter":               "TouchEnter",
		"touchleave":               "TouchLeave",
		"touchmove":                "TouchMove",
		"touchstart":               "TouchStart",
		"transitionend":            "TransitionEnd",
		"updateready":              "UpdateReady",
		"upgradeneeded":            "UpgradeNeeded",
		"userproximity":            "UserProximity",
		"versionchange":            "VersionChange",
		"visibilitychange":         "VisibilityChange",
		"voiceschanged":            "VoicesChanged",
		"volumechange":             "VolumeChange",
		"vrdisplayconnected":       "VRDisplayConnected",
		"vrdisplaydisconnected":    "VRDisplayDisconnected",
		"vrdisplaypresentchange":   "VRDisplayPresentChange",
	}

	doc, err := goquery.NewDocument("https://developer.mozilla.org/en-US/docs/Web/Events")
	if err != nil {
		panic(err)
	}

	events := make(map[string]*Event)

	doc.Find(".standard-table").Eq(0).Find("tr").Each(func(i int, s *goquery.Selection) {
		cols := s.Find("td")
		if cols.Length() == 0 || cols.Find(".icon-thumbs-down-alt").Length() != 0 {
			return
		}
		link := cols.Eq(0).Find("a").Eq(0)
		var e Event
		e.Name = link.Text()
		e.Link, _ = link.Attr("href")
		e.Desc = strings.TrimSpace(cols.Eq(3).Text())
		if e.Desc == "" {
			e.Desc = "(no documentation)"
		}
		e.Spec = strings.TrimSpace(cols.Eq(2).Text())

		funName := nameMap[e.Name]
		if funName == "" {
			funName = capitalize(e.Name)
		}

		events[funName] = &e
	})

	var names []string
	for name := range events {
		names = append(names, name)
	}
	sort.Strings(names)

	file, err := os.Create("event.gen.go")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	fmt.Fprint(file, `//go:generate go run generate.go

// Package event defines markup to bind DOM events.
//
// Generated from "Event reference" by Mozilla Contributors,
// https://developer.mozilla.org/en-US/docs/Web/Events, licensed under
// CC-BY-SA 2.5.
package event

import "github.com/gopherjs/vecty"
`)

	for _, name := range names {
		e := events[name]
		if e.Spec == "WebVR API" {
			continue // not stabilized
		}
		fmt.Fprintf(file, `%s
//
// https://developer.mozilla.org%s
func %s(listener func(*vecty.Event)) *vecty.EventListener {
	return &vecty.EventListener{Name: "%s", Listener: listener}
}
`, descToComments(e.Desc), e.Link[6:], name, e.Name)
	}
}

func capitalize(s string) string {
	return strings.ToUpper(s[:1]) + s[1:]
}

func descToComments(desc string) string {
	c := ""
	length := 80
	for _, word := range strings.Split(desc, " ") {
		if length+len(word)+1 > 80 {
			length = 3
			c += "\n//"
		}
		c += " " + word
		length += len(word) + 1
	}
	return c
}
<|endoftext|>