<commit_before>package main

import (
	"fmt"
	"strings"

	humanize "github.com/dustin/go-humanize"
	"github.com/gizak/termui"
	"github.com/luan/idope/fetcher"
)

type UI struct {
	selectedIndex int
	listContent   []string
	listOffset    int

	listWidget *termui.List
}

func NewUI() *UI {
	return &UI{}
}

func (ui *UI) Render() {
	ui.listWidget.Items = ui.selectItem()
	termui.Render(termui.Body)
}

func (ui *UI) Setup() {
	err := termui.Init()
	if err != nil {
		panic(err)
	}

	list := termui.NewList()
	list.BorderLabel = "LRPs"
	list.ItemFgColor = termui.ColorYellow
	ui.listWidget = list
	termui.Body.AddRows(
		termui.NewRow(
			termui.NewCol(9, 0, ui.listWidget),
		),
	)
	ui.listWidget.Height = termui.TermHeight()
	termui.Body.Align()

	ui.Render()
	ui.bindEvents()
}

func (ui *UI) Close() {
	termui.Close()
}

func (ui *UI) Loop() {
	termui.Loop()
}

func colorizeState(state string) string {
	switch state {
	case "UNCLAIMED":
		return "[UNCLAIMED](fg-white)"
	case "CLAIMED":
		return "[CLAIMED](fg-yellow)"
	case "RUNNING":
		return "[RUNNING](fg-green)"
	case "CRASHED":
		return "[CRASHED](fg-red)"
	default:
		return state
	}
}

func fmtBytes(s uint64) string {
	return strings.Replace(humanize.Bytes(s), " ", "", -1)
}

func lrpToStrings(lrp *fetcher.LRP) []string {
	ret := []string{}
	ret = append(ret,
		fmt.Sprintf(
			"guid: [%s](fg-bold)\t[instances:](fg-white) [%d](fg-white,fg-bold) ",
			lrp.Desired.ProcessGuid[:8], lrp.Desired.Instances,
		),
	)
	for _, actual := range lrp.ActualLRPsByCPU(true) {
		state := colorizeState(actual.ActualLRP.State)
		ret = append(ret,
			fmt.Sprintf(
				"\t[%2d](fg-white) %s %12s [%5.1f%%](fg-magenta) [%6s](fg-cyan)/[%-6s](fg-cyan,fg-bold) [%6s](fg-red)/[%-6s](fg-red,fg-bold)",
				actual.ActualLRP.Index, actual.ActualLRP.CellId, state,
				actual.Metrics.CPU*100,
				fmtBytes(actual.Metrics.Memory), fmtBytes(uint64(lrp.Desired.MemoryMb*1000*1000)),
				fmtBytes(actual.Metrics.Disk), fmtBytes(uint64(lrp.Desired.DiskMb*1000*1000)),
			))
	}
	return ret
}

func (ui *UI) SetState(state *fetcher.Data) {
	ui.listContent = []string{}
	lrps := state.LRPs.SortedByProcessGuid()
	for _, lrp := range lrps {
		content := lrpToStrings(lrp)
		ui.listContent = append(ui.listContent, content...)
	}
	ui.Render()
}

func (ui *UI) bindEvents() {
	termui.Handle("/sys/kbd/q", func(termui.Event) {
		termui.StopLoop()
	})

	termui.Handle("/sys/kbd/j", ui.handleDown)
	termui.Handle("/sys/kbd/<down>", ui.handleDown)
	termui.Handle("/sys/kbd/k", ui.handleUp)
	termui.Handle("/sys/kbd/<up>", ui.handleUp)
	termui.Handle("/sys/kbd/g", ui.handleTop)
	termui.Handle("/sys/kbd/<home>", ui.handleTop)
	termui.Handle("/sys/kbd/G", ui.handleBottom)
	termui.Handle("/sys/kbd/<end>", ui.handleBottom)

	termui.Handle("/sys/wnd/resize", func(termui.Event) {
		ui.listWidget.Height = termui.TermHeight()
		termui.Body.Align()
		ui.Render()
	})
}

func (ui *UI) handleBottom(_ termui.Event) {
	visibleHeight := ui.listWidget.InnerHeight()
	totalHeight := len(ui.listContent)
	if totalHeight < visibleHeight {
		visibleHeight = totalHeight
	}
	ui.selectedIndex = visibleHeight - 1
	ui.listOffset = totalHeight - visibleHeight
	ui.Render()
}

func (ui *UI) handleTop(_ termui.Event) {
	ui.selectedIndex = 0
	ui.listOffset = 0
	ui.Render()
}

func (ui *UI) handleDown(_ termui.Event) {
	visibleHeight := ui.listWidget.InnerHeight()
	totalHeight := len(ui.listContent)
	if totalHeight < visibleHeight {
		visibleHeight = totalHeight
	}

	if ui.selectedIndex < visibleHeight-1 {
		ui.selectedIndex++
	} else if ui.listOffset < totalHeight-visibleHeight-1 {
		ui.listOffset++
	}
	ui.Render()
}

func (ui *UI) handleUp(_ termui.Event) {
	if ui.selectedIndex > 0 {
		ui.selectedIndex--
	} else if ui.listOffset > 0 {
		ui.listOffset--
	}
	ui.Render()
}

func (ui *UI) selectItem() []string {
	if ui.listWidget.InnerHeight() == 0 {
		return []string{}
	}
	index := ui.selectedIndex
	visibleContent := ui.listContent[ui.listOffset:]

	ret := make([]string, len(visibleContent))
	for i, item := range visibleContent {
		if i == index {
			ret[i] = fmt.Sprintf(" [➤](fg-cyan,fg-bold) %s", item)
		} else {
			ret[i] = fmt.Sprintf("   %s", item)
		}
	}
	return ret
}
<commit_msg>add header<commit_after>package main

import (
	"fmt"
	"strings"

	humanize "github.com/dustin/go-humanize"
	"github.com/gizak/termui"
	"github.com/luan/idope/fetcher"
)

type UI struct {
	selectedIndex int
	listContent   []string
	listOffset    int

	listWidget *termui.List
}

func NewUI() *UI {
	return &UI{}
}

func (ui *UI) Render() {
	ui.listWidget.Items = ui.selectItem()
	termui.Render(termui.Body)
}

func (ui *UI) Setup() {
	err := termui.Init()
	if err != nil {
		panic(err)
	}

	list := termui.NewList()
	list.BorderLabel = "LRPs"
	list.ItemFgColor = termui.ColorYellow
	ui.listWidget = list
	termui.Body.AddRows(
		termui.NewRow(
			termui.NewCol(9, 0, ui.listWidget),
		),
	)
	ui.listWidget.Height = termui.TermHeight()
	termui.Body.Align()

	ui.Render()
	ui.bindEvents()
}

func (ui *UI) Close() {
	termui.Close()
}

func (ui *UI) Loop() {
	termui.Loop()
}

func colorizeState(state string) string {
	switch state {
	case "UNCLAIMED":
		return fmt.Sprintf("[%-10s](fg-white)", state)
	case "CLAIMED":
		return fmt.Sprintf("[%-10s](fg-yellow)", state)
	case "RUNNING":
		return fmt.Sprintf("[%-10s](fg-green)", state)
	case "CRASHED":
		return fmt.Sprintf("[%-10s](fg-red)", state)
	default:
		return state
	}
}

func fmtBytes(s uint64) string {
	return strings.Replace(humanize.Bytes(s), " ", "", -1)
}

func fmtCell(s string) string {
	parts := strings.Split(s, "_")
	if len(parts) != 2 {
		return "none"
	}
	parts = strings.Split(parts[1], "-")
	return fmt.Sprintf("%s/%s", parts[0], parts[1])
}

func lrpToStrings(lrp *fetcher.LRP) []string {
	ret := []string{}
	ret = append(ret,
		fmt.Sprintf(
			"guid: [%s](fg-bold)\t[instances:](fg-white) [%d](fg-white,fg-bold) ",
			lrp.Desired.ProcessGuid[:8], lrp.Desired.Instances,
		),
	)
	ret = append(ret,
		fmt.Sprintf(
			"    %s %s %s %s %s %s",
			"[index](fg-white,bg-reverse)",
			"[cell  ](fg-yellow,bg-reverse)",
			"[state     ](fg-white,bg-reverse)",
			"[cpu   ](fg-magenta,bg-reverse)",
			"[memory](fg-cyan,bg-reverse)[/total    ](fg-cyan,bg-reverse)",
			"[disk](fg-red,bg-reverse)[/total       ](fg-red,bg-reverse)",
		),
	)
	for _, actual := range lrp.ActualLRPsByCPU(true) {
		state := colorizeState(actual.ActualLRP.State)
		ret = append(ret,
			fmt.Sprintf(
				"    [%5d](fg-white) %-6s %s [%5.1f%%](fg-magenta) [%8s](fg-cyan)[/%-8s](fg-cyan,fg-bold) [%8s](fg-red)[/%-8s](fg-red,fg-bold)",
				actual.ActualLRP.Index, fmtCell(actual.ActualLRP.CellId), state,
				actual.Metrics.CPU*100,
				fmtBytes(actual.Metrics.Memory), fmtBytes(uint64(lrp.Desired.MemoryMb*1000*1000)),
				fmtBytes(actual.Metrics.Disk), fmtBytes(uint64(lrp.Desired.DiskMb*1000*1000)),
			))
	}
	return ret
}

func (ui *UI) SetState(state *fetcher.Data) {
	ui.listContent = []string{}
	lrps := state.LRPs.SortedByProcessGuid()
	for _, lrp := range lrps {
		content := lrpToStrings(lrp)
		ui.listContent = append(ui.listContent, content...)
	}
	ui.Render()
}

func (ui *UI) bindEvents() {
	termui.Handle("/sys/kbd/q", func(termui.Event) {
		termui.StopLoop()
	})

	termui.Handle("/sys/kbd/j", ui.handleDown)
	termui.Handle("/sys/kbd/<down>", ui.handleDown)
	termui.Handle("/sys/kbd/k", ui.handleUp)
	termui.Handle("/sys/kbd/<up>", ui.handleUp)
	termui.Handle("/sys/kbd/g", ui.handleTop)
	termui.Handle("/sys/kbd/<home>", ui.handleTop)
	termui.Handle("/sys/kbd/G", ui.handleBottom)
	termui.Handle("/sys/kbd/<end>", ui.handleBottom)

	termui.Handle("/sys/wnd/resize", func(termui.Event) {
		ui.listWidget.Height = termui.TermHeight()
		termui.Body.Align()
		ui.Render()
	})
}

func (ui *UI) handleBottom(_ termui.Event) {
	visibleHeight := ui.listWidget.InnerHeight()
	totalHeight := len(ui.listContent)
	if totalHeight < visibleHeight {
		visibleHeight = totalHeight
	}
	ui.selectedIndex = visibleHeight - 1
	ui.listOffset = totalHeight - visibleHeight
	ui.Render()
}

func (ui *UI) handleTop(_ termui.Event) {
	ui.selectedIndex = 0
	ui.listOffset = 0
	ui.Render()
}

func (ui *UI) handleDown(_ termui.Event) {
	visibleHeight := ui.listWidget.InnerHeight()
	totalHeight := len(ui.listContent)
	if totalHeight < visibleHeight {
		visibleHeight = totalHeight
	}

	if ui.selectedIndex < visibleHeight-1 {
		ui.selectedIndex++
	} else if ui.listOffset < totalHeight-visibleHeight-1 {
		ui.listOffset++
	}
	ui.Render()
}

func (ui *UI) handleUp(_ termui.Event) {
	if ui.selectedIndex > 0 {
		ui.selectedIndex--
	} else if ui.listOffset > 0 {
		ui.listOffset--
	}
	ui.Render()
}

func (ui *UI) selectItem() []string {
	if ui.listWidget.InnerHeight() == 0 {
		return []string{}
	}
	index := ui.selectedIndex
	visibleContent := ui.listContent[ui.listOffset:]

	ret := make([]string, len(visibleContent))
	for i, item := range visibleContent {
		if i == index {
			ret[i] = fmt.Sprintf(" [➤](fg-cyan,fg-bold) %s", item)
		} else {
			ret[i] = fmt.Sprintf("   %s", item)
		}
	}
	return ret
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"strconv"
	"os"
	stfl "github.com/akrennmair/go-stfl"
)

type UserInterface struct {
	form                  *stfl.Form
	actionchan            chan UserInterfaceAction
	tweetchan             chan []*Tweet
	cmdchan               chan TwitterCommand
	lookupchan            chan TweetRequest
	in_reply_to_status_id int64
}

type ActionId int

const (
	RESET_LAST_LINE ActionId = iota
	RAW_INPUT
	UPDATE_RATELIMIT
	KEY_PRESS
)

type UserInterfaceAction struct {
	Action ActionId
	Args   []string
}

func NewUserInterface(cc chan TwitterCommand, tc chan []*Tweet, lc chan TweetRequest, uac chan UserInterfaceAction) *UserInterface {
	stfl.Init()
	ui := &UserInterface{
		form:                  stfl.Create("<ui.stfl>"),
		actionchan:            uac,
		tweetchan:             tc,
		cmdchan:               cc,
		in_reply_to_status_id: 0,
		lookupchan:            lc,
	}
	ui.form.Set("program", "gockel 0.0")
	return ui
}

func (ui *UserInterface) GetActionChannel() chan UserInterfaceAction {
	return ui.actionchan
}

func (ui *UserInterface) Run() {
	for {
		select {
		case newtweets := <-ui.tweetchan:
			str := formatTweets(newtweets)
			ui.form.Modify("tweets", "insert_inner", str)
			ui.form.Run(-1)
		case action := <-ui.actionchan:
			ui.HandleAction(action)
		}
	}
}

func (ui *UserInterface) HandleAction(action UserInterfaceAction) {
	switch action.Action {
	case RESET_LAST_LINE:
		ui.ResetLastLine()
	case RAW_INPUT:
		input := action.Args[0]
		ui.HandleRawInput(input)
	case UPDATE_RATELIMIT:
		rem, _ := strconv.Atoui(action.Args[0])
		limit, _ := strconv.Atoui(action.Args[1])
		reset, _ := strconv.Atoi64(action.Args[2])
		newtext := fmt.Sprintf("Next reset: %d min %d/%d", reset/60, rem, limit)
		ui.form.Set("rateinfo", newtext)
		ui.form.Run(-1)
	case KEY_PRESS:
		ui.UpdateInfoLine()
		ui.form.Run(-1)
	}
}

func (ui *UserInterface) ResetLastLine() {
	ui.form.Modify("lastline", "replace", "{hbox[lastline] .expand:0 {label text[msg]:\"\" .expand:h}}")
}

func (ui *UserInterface) UpdateInfoLine() {
	status_id, err := strconv.Atoi64(ui.form.Get("status_id"))
	if err != nil {
		return
	}

	tweet := ui.LookupTweet(status_id)
	if tweet != nil {
		var screen_name, real_name, location, posttime string
		if tweet.User != nil {
			if tweet.User.Screen_name != nil {
				screen_name = *tweet.User.Screen_name
			}
			if tweet.User.Name != nil {
				real_name = *tweet.User.Name
			}
			if tweet.User.Location != nil && *tweet.User.Location != "" {
				location = " - "+*tweet.User.Location
			}
		}
		if tweet.Created_at != nil {
			posttime = *tweet.Created_at
		}
		infoline := fmt.Sprintf(">> @%s (%s)%s | posted %s | https://twitter.com/%s/statuses/%d", screen_name, real_name, location, posttime, screen_name, status_id)
		ui.form.Set("infoline", infoline)
	}
}

func (ui *UserInterface) HandleRawInput(input string) {
	switch input {
	case "ENTER":
		ui.SetInputField("Tweet: ", "", "end-input")
	case "r":
		var err os.Error
		ui.in_reply_to_status_id, err = strconv.Atoi64(ui.form.Get("status_id"))
		if err != nil {
			// TODO: show error
			break
		}
		tweet := ui.LookupTweet(ui.in_reply_to_status_id)
		if tweet != nil {
			ui.SetInputField("Reply: ", "@"+*tweet.User.Screen_name+" ","end-input")
		} else {
			//TODO: show error
		}
	case "^R":
		status_id, err := strconv.Atoi64(ui.form.Get("status_id"))
		if err != nil {
			// TODO: show error
			break
		}
		status_id_ptr := new(int64)
		*status_id_ptr = status_id
		ui.cmdchan <- TwitterCommand{Cmd: RETWEET, Data: Tweet{Id: status_id_ptr}}
	case "end-input":
		tweet_text := new(string)
		*tweet_text = ui.form.Get("inputfield")
		if len(*tweet_text) > 0 {
			t := Tweet{Text: tweet_text}
			if ui.in_reply_to_status_id != 0 {
				t.In_reply_to_status_id = new(int64)
				*t.In_reply_to_status_id = ui.in_reply_to_status_id
				ui.in_reply_to_status_id = int64(0)
			}
			ui.cmdchan <- TwitterCommand{Cmd: UPDATE, Data: t}
		}
		ui.ResetLastLine()
	case "cancel-input":
		ui.ResetLastLine()
	}
	ui.form.Run(-1)
}

func (ui *UserInterface) LookupTweet(status_id int64) *Tweet {
	reply := make(chan *Tweet)
	req := TweetRequest{Status_id: status_id, Reply: reply}
	ui.lookupchan <- req
	return <-reply
}

func (ui *UserInterface) InputLoop() {
	event := ""
	for event != "q" {
		event = ui.form.Run(0)
		if event != "" {
			if event == "^L" {
				stfl.Reset()
			} else {
				ui.actionchan <- UserInterfaceAction{RAW_INPUT, []string{event}}
			}
		} else {
			ui.actionchan <- UserInterfaceAction{Action:KEY_PRESS}
		}
	}
	stfl.Reset()
}

func (ui *UserInterface) SetInputField(prompt, deftext, endevent string) {
	last_line_text := "{hbox[lastline] .expand:0 {label .expand:0 text[prompt]:" + stfl.Quote(prompt) + "}{input[tweetinput] on_ESC:cancel-input on_ENTER:" + endevent + " modal:1 .expand:h text[inputfield]:" + stfl.Quote(deftext) + "}}"

	ui.form.Modify("lastline", "replace", last_line_text)
	ui.form.SetFocus("tweetinput")
}

func formatTweets(tweets []*Tweet) string {
	buf := bytes.NewBufferString("{list")

	for _, t := range tweets {
		tweetline := fmt.Sprintf("[%16s] %s", "@"+*t.User.Screen_name, *t.Text)
		buf.WriteString(fmt.Sprintf("{listitem[%v] text:%v}", *t.Id, stfl.Quote(tweetline)))
	}

	buf.WriteString("}")
	return string(buf.Bytes())
}
<commit_msg>unescape tweet text.<commit_after>package main

import (
	"bytes"
	"fmt"
	"strconv"
	"os"
	"html"
	stfl "github.com/akrennmair/go-stfl"
)

type UserInterface struct {
	form                  *stfl.Form
	actionchan            chan UserInterfaceAction
	tweetchan             chan []*Tweet
	cmdchan               chan TwitterCommand
	lookupchan            chan TweetRequest
	in_reply_to_status_id int64
}

type ActionId int

const (
	RESET_LAST_LINE ActionId = iota
	RAW_INPUT
	UPDATE_RATELIMIT
	KEY_PRESS
)

type UserInterfaceAction struct {
	Action ActionId
	Args   []string
}

func NewUserInterface(cc chan TwitterCommand, tc chan []*Tweet, lc chan TweetRequest, uac chan UserInterfaceAction) *UserInterface {
	stfl.Init()
	ui := &UserInterface{
		form:                  stfl.Create("<ui.stfl>"),
		actionchan:            uac,
		tweetchan:             tc,
		cmdchan:               cc,
		in_reply_to_status_id: 0,
		lookupchan:            lc,
	}
	ui.form.Set("program", "gockel 0.0")
	return ui
}

func (ui *UserInterface) GetActionChannel() chan UserInterfaceAction {
	return ui.actionchan
}

func (ui *UserInterface) Run() {
	for {
		select {
		case newtweets := <-ui.tweetchan:
			str := formatTweets(newtweets)
			ui.form.Modify("tweets", "insert_inner", str)
			ui.form.Run(-1)
		case action := <-ui.actionchan:
			ui.HandleAction(action)
		}
	}
}

func (ui *UserInterface) HandleAction(action UserInterfaceAction) {
	switch action.Action {
	case RESET_LAST_LINE:
		ui.ResetLastLine()
	case RAW_INPUT:
		input := action.Args[0]
		ui.HandleRawInput(input)
	case UPDATE_RATELIMIT:
		rem, _ := strconv.Atoui(action.Args[0])
		limit, _ := strconv.Atoui(action.Args[1])
		reset, _ := strconv.Atoi64(action.Args[2])
		newtext := fmt.Sprintf("Next reset: %d min %d/%d", reset/60, rem, limit)
		ui.form.Set("rateinfo", newtext)
		ui.form.Run(-1)
	case KEY_PRESS:
		ui.UpdateInfoLine()
		ui.form.Run(-1)
	}
}

func (ui *UserInterface) ResetLastLine() {
	ui.form.Modify("lastline", "replace", "{hbox[lastline] .expand:0 {label text[msg]:\"\" .expand:h}}")
}

func (ui *UserInterface) UpdateInfoLine() {
	status_id, err := strconv.Atoi64(ui.form.Get("status_id"))
	if err != nil {
		return
	}

	tweet := ui.LookupTweet(status_id)
	if tweet != nil {
		var screen_name, real_name, location, posttime string
		if tweet.User != nil {
			if tweet.User.Screen_name != nil {
				screen_name = *tweet.User.Screen_name
			}
			if tweet.User.Name != nil {
				real_name = *tweet.User.Name
			}
			if tweet.User.Location != nil && *tweet.User.Location != "" {
				location = " - "+*tweet.User.Location
			}
		}
		if tweet.Created_at != nil {
			posttime = *tweet.Created_at
		}
		infoline := fmt.Sprintf(">> @%s (%s)%s | posted %s | https://twitter.com/%s/statuses/%d", screen_name, real_name, location, posttime, screen_name, status_id)
		ui.form.Set("infoline", infoline)
	}
}

func (ui *UserInterface) HandleRawInput(input string) {
	switch input {
	case "ENTER":
		ui.SetInputField("Tweet: ", "", "end-input")
	case "r":
		var err os.Error
		ui.in_reply_to_status_id, err = strconv.Atoi64(ui.form.Get("status_id"))
		if err != nil {
			// TODO: show error
			break
		}
		tweet := ui.LookupTweet(ui.in_reply_to_status_id)
		if tweet != nil {
			ui.SetInputField("Reply: ", "@"+*tweet.User.Screen_name+" ","end-input")
		} else {
			//TODO: show error
		}
	case "^R":
		status_id, err := strconv.Atoi64(ui.form.Get("status_id"))
		if err != nil {
			// TODO: show error
			break
		}
		status_id_ptr := new(int64)
		*status_id_ptr = status_id
		ui.cmdchan <- TwitterCommand{Cmd: RETWEET, Data: Tweet{Id: status_id_ptr}}
	case "end-input":
		tweet_text := new(string)
		*tweet_text = ui.form.Get("inputfield")
		if len(*tweet_text) > 0 {
			t := Tweet{Text: tweet_text}
			if ui.in_reply_to_status_id != 0 {
				t.In_reply_to_status_id = new(int64)
				*t.In_reply_to_status_id = ui.in_reply_to_status_id
				ui.in_reply_to_status_id = int64(0)
			}
			ui.cmdchan <- TwitterCommand{Cmd: UPDATE, Data: t}
		}
		ui.ResetLastLine()
	case "cancel-input":
		ui.ResetLastLine()
	}
	ui.form.Run(-1)
}

func (ui *UserInterface) LookupTweet(status_id int64) *Tweet {
	reply := make(chan *Tweet)
	req := TweetRequest{Status_id: status_id, Reply: reply}
	ui.lookupchan <- req
	return <-reply
}

func (ui *UserInterface) InputLoop() {
	event := ""
	for event != "q" {
		event = ui.form.Run(0)
		if event != "" {
			if event == "^L" {
				stfl.Reset()
			} else {
				ui.actionchan <- UserInterfaceAction{RAW_INPUT, []string{event}}
			}
		} else {
			ui.actionchan <- UserInterfaceAction{Action:KEY_PRESS}
		}
	}
	stfl.Reset()
}

func (ui *UserInterface) SetInputField(prompt, deftext, endevent string) {
	last_line_text := "{hbox[lastline] .expand:0 {label .expand:0 text[prompt]:" + stfl.Quote(prompt) + "}{input[tweetinput] on_ESC:cancel-input on_ENTER:" + endevent + " modal:1 .expand:h text[inputfield]:" + stfl.Quote(deftext) + "}}"

	ui.form.Modify("lastline", "replace", last_line_text)
	ui.form.SetFocus("tweetinput")
}

func formatTweets(tweets []*Tweet) string {
	buf := bytes.NewBufferString("{list")

	for _, t := range tweets {
		tweetline := fmt.Sprintf("[%16s] %s", "@"+*t.User.Screen_name, *t.Text)
		buf.WriteString(fmt.Sprintf("{listitem[%v] text:%v}", *t.Id, stfl.Quote(html.UnescapeString(tweetline))))
	}

	buf.WriteString("}")
	return string(buf.Bytes())
}
<|endoftext|>


<commit_before>package main

import "fmt"
import "strings"
import "github.com/nsf/termbox-go"
import "strconv"

const MAX_CELL_WIDTH = 20
const HILITE_FG = termbox.ColorBlack | termbox.AttrBold
const HILITE_BG = termbox.ColorWhite

type inputMode int

const (
	ModeDefault = iota
	ModeFilter
	ModeColumnSelect
	ModeRowSelect
)

// It is so dumb that go doesn't have this
func clamp(val, lo, hi int) int {
	if val <= lo {
		return lo
	} else if val >= hi {
		return hi
	}

	return val
}

var pinnedBounds = 0

func writeString(x, y int, fg, bg termbox.Attribute, msg string) int {
	for _, c := range msg {
		if x >= pinnedBounds {
			termbox.SetCell(x, y, c, fg, bg)
		}
		x += 1
	}
	return x
}

func writeLine(x, y int, fg, bg termbox.Attribute, line string) {
	width, _ := termbox.Size()
	for _, c := range line {
		termbox.SetCell(x, y, c, fg, bg)
		x += 1
	}
	for i := x; i < width; i += 1 {
		termbox.SetCell(x+i, y, ' ', fg, bg)
	}
}

var cellFmtString = "%-" + strconv.Itoa(MAX_CELL_WIDTH) + "s"

func (ui *UI) writeCell(cell string, x, y, index int, fg, bg termbox.Attribute) int {
	colOpts := ui.columnOpts[index]
	lastCol := index == len(ui.columnOpts)-1

	if index == ui.colIdx && ui.mode == ModeColumnSelect {
		fg = HILITE_FG
		bg = HILITE_BG
	}

	if colOpts.collapsed {
		x = writeString(x, y, fg, bg, "…")
	} else if !colOpts.expanded && len(cell) < MAX_CELL_WIDTH {
		padded := fmt.Sprintf(cellFmtString, cell)
		x = writeString(x, y, fg, bg, padded)
	} else if !colOpts.expanded && !lastCol {
		width := clamp(len(cell)-1, 0, MAX_CELL_WIDTH-1)
		x = writeString(x, y, fg, bg, cell[:width])
		x = writeString(x, y, fg, bg, "…")
	} else {
		writeString(x, y, fg, bg, cell)
		x += colOpts.width
	}

	// Draw separator if this isn't the last element
	if index != len(ui.columns)-1 {
		x = writeString(x, y, termbox.ColorRed, termbox.ColorDefault, " │ ")
	}

	return x
}

func (ui *UI) writePinned(y int, fg, bg termbox.Attribute, row []string) int {
	// ignore our view offsets
	pinnedBounds = 0

	for i, cell := range row {
		colOpts := ui.columnOpts[i]

		if colOpts.pinned {
			pinnedBounds = ui.writeCell(cell, pinnedBounds, y, i, fg, bg)
		}
	}

	return pinnedBounds
}

func (ui *UI) writeColumns(x, y int) {
	var fg, bg termbox.Attribute

	x += ui.writePinned(y, termbox.ColorWhite, termbox.ColorDefault, ui.columns)

	for i, col := range ui.columns {
		colOpts := ui.columnOpts[i]

		fg = termbox.ColorBlack | termbox.AttrBold
		bg = termbox.ColorWhite

		if !colOpts.pinned {
			x = ui.writeCell(col, x, y, i, fg, bg)
		}
	}

	endOfLinePosition = x
}

func (ui *UI) writeRow(x, y int, row []string) {
	fg := termbox.ColorDefault

	if ui.zebraStripe && y%2 == 0 {
		fg = termbox.ColorMagenta
	}

	x += ui.writePinned(y, termbox.ColorCyan, termbox.ColorBlack, row)

	for i, _ := range ui.columns {
		colOpts := ui.columnOpts[i]

		if !colOpts.pinned {
			x = ui.writeCell(row[i], x, y, i, fg, termbox.ColorDefault)
		}
	}
}

type columnOptions struct {
	expanded  bool
	collapsed bool
	pinned    bool
	width     int
}

type UI struct {
	mode             inputMode
	rowIdx, colIdx   int // Selection control
	offsetX, offsetY int // Pan control
	filterString     string
	zebraStripe      bool
	columnOpts       []columnOptions
	columns          []string
	rows             [][]string
	width            int
}

func NewUi(data TabularData) UI {
	colOpts := make([]columnOptions, len(data.Columns))
	columns := make([]string, len(data.Columns))

	for i, col := range data.Columns {
		columns[i] = col.Name
		colOpts[i] = columnOptions{
			expanded:  col.Width < MAX_CELL_WIDTH,
			collapsed: false,
			pinned:    false,
			width:     col.Width,
		}
	}

	return UI{
		offsetX:     0,
		offsetY:     0,
		mode:        ModeDefault,
		colIdx:      -1,
		columnOpts:  colOpts,
		rows:        data.Rows,
		columns:     columns,
		width:       data.Width,
		zebraStripe: false,
	}
}

func (ui *UI) Init() error {
	if err := termbox.Init(); err != nil {
		return err
	}

	termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse)

	return nil
}

func (ui *UI) Loop() {
	defer termbox.Close()

	ui.repaint()

eventloop:
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			if ev.Key == termbox.KeyCtrlC {
				break eventloop
			}

			switch ui.mode {
			case ModeFilter:
				ui.handleKeyFilter(ev)
			case ModeColumnSelect:
				ui.handleKeyColumnSelect(ev)
			default:
				ui.handleKeyDefault(ev)
			}
		}

		ui.repaint()
	}
}

// Return indices of rows to display
func (ui *UI) filterRows(num int) []int {
	rows := make([]int, 0, num)

	// fast pass
	if ui.filterString == "" {
		for i := 0; i < num; i += 1 {
			if i+ui.offsetY >= len(ui.rows) {
				break
			}
			rows = append(rows, i+ui.offsetY)
		}
	} else {
		for i := 0; i < num; i += 1 {
			if i+ui.offsetY >= len(ui.rows) {
				break
			}

			for _, col := range ui.rows[i+ui.offsetY] {
				if strings.Contains(col, ui.filterString) {
					rows = append(rows, i+ui.offsetY)
					break
				}
			}
		}
	}

	return rows
}

func (ui *UI) repaint() {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	termbox.HideCursor()
	_, height := termbox.Size()

	const coldef = termbox.ColorDefault

	ui.writeColumns(ui.offsetX+0, 0)

	rowIdx := ui.filterRows(height - 2)

	for i := 0; i < height-2; i += 1 {
		if i < len(rowIdx) {
			ui.writeRow(ui.offsetX+0, i+1, ui.rows[rowIdx[i]])
		} else {
			writeLine(0, i+1, termbox.ColorWhite|termbox.AttrBold, termbox.ColorBlack, "~")
		}
	}

	switch ui.mode {
	case ModeFilter:
		ext := ""
		if len(rowIdx) == height-2 {
			ext = "+"
		}
		line := fmt.Sprintf("FILTER [%d%s matches]: %s", len(rowIdx), ext, ui.filterString)
		writeLine(0, height-1, termbox.ColorWhite|termbox.AttrBold, termbox.ColorDefault, line)
		termbox.SetCursor(len(line), height-1)
	case ModeColumnSelect:
		line := "COLUMN SELECT (^g quit) [" + ui.columns[ui.colIdx] + "]"
		writeLine(0, height-1, termbox.ColorWhite|termbox.AttrBold, termbox.ColorDefault, line)
	default:
		first := 0
		last := 0
		total := len(ui.rows) - 1
		filter := ""

		if len(rowIdx) >= 2 {
			first = rowIdx[0]
			last = rowIdx[len(rowIdx)-1]
		}

		if ui.filterString != "" {
			filter = fmt.Sprintf("[filter: \"%s\"] ", ui.filterString)
		}

		line := fmt.Sprintf("%s[rows %d-%d of %d] :", filter, first, last, total)
		writeLine(0, height-1, termbox.ColorDefault, termbox.ColorDefault, line)
	}

	termbox.Flush()
}

func (ui *UI) handleKeyFilter(ev termbox.Event) {
	// Ch == 0 implies this was a special key
	if ev.Ch == 0 && ev.Key != termbox.KeySpace {
		if ev.Key == termbox.KeyEsc || ev.Key == termbox.KeyCtrlG || ev.Key == termbox.KeyEnter {
			ui.mode = ModeDefault
		} else if ev.Key == termbox.KeyDelete || ev.Key == termbox.KeyBackspace ||
			ev.Key == termbox.KeyBackspace2 {
			if sz := len(ui.filterString); sz > 0 {
				ui.filterString = ui.filterString[:sz-1]
			}
		} else {
			// Fallback to default handling for arrows etc
			ui.handleKeyDefault(ev)
		}
		return
	}

	if ev.Key == termbox.KeySpace {
		ui.filterString += " "
	} else {
		ui.filterString += string(ev.Ch)
	}

	ui.offsetY = 0
}

var globalExpanded = false
var endOfLinePosition = 0

func (ui *UI) handleKeyColumnSelect(ev termbox.Event) {
	switch {
	case ev.Key == termbox.KeyArrowRight:
		ui.colIdx = clamp(ui.colIdx+1, 0, len(ui.columns)-1)
	case ev.Key == termbox.KeyArrowLeft:
		ui.colIdx = clamp(ui.colIdx-1, 0, len(ui.columns)-1)
	case ev.Ch == 'w':
		ui.columnOpts[ui.colIdx].collapsed = !ui.columnOpts[ui.colIdx].collapsed
	case ev.Ch == 'x':
		ui.columnOpts[ui.colIdx].expanded = !ui.columnOpts[ui.colIdx].expanded
		if ui.columnOpts[ui.colIdx].expanded {
			ui.columnOpts[ui.colIdx].collapsed = false
		}
	case ev.Ch == '.':
		ui.columnOpts[ui.colIdx].pinned = !ui.columnOpts[ui.colIdx].pinned

		if ui.columnOpts[ui.colIdx].pinned {
			ui.columnOpts[ui.colIdx].collapsed = false
		}

	case ev.Key == termbox.KeyCtrlG, ev.Key == termbox.KeyEsc:
		ui.mode = ModeDefault
	default:
		ui.handleKeyDefault(ev)
	}

	// find if we've gone off screen and readjust
	// TODO: this bit is buggy
	cursorPosition := 0
	for i, _ := range ui.columns {
		colOpts := ui.columnOpts[i]

		if i == ui.colIdx {
			break
		}
		//cursorPosition += 3
		if !colOpts.collapsed {
			cursorPosition += colOpts.width
		}
	}

	width, _ := termbox.Size()
	if cursorPosition > width-ui.offsetX || cursorPosition < -ui.offsetX {
		ui.offsetX = -cursorPosition
	}
}

func (ui *UI) handleKeyDefault(ev termbox.Event) {
	switch {
	case ev.Key == termbox.KeyCtrlA:
		ui.offsetX = 0
	case ev.Key == termbox.KeyCtrlE:
		// FIXME: this is buggy
		w, _ := termbox.Size()
		ui.offsetX = -endOfLinePosition + w
	case ev.Key == termbox.KeyArrowRight:
		ui.offsetX = clamp(ui.offsetX-5, -endOfLinePosition, 0)
	case ev.Key == termbox.KeyArrowLeft:
		ui.offsetX = clamp(ui.offsetX+5, -endOfLinePosition, 0)
	case ev.Key == termbox.KeyArrowUp:
		ui.offsetY = clamp(ui.offsetY-1, 0, len(ui.rows))
	case ev.Key == termbox.KeyArrowDown:
		ui.offsetY = clamp(ui.offsetY+1, 0, len(ui.rows))
	case ev.Ch == '/', ev.Key == termbox.KeyCtrlR:
		ui.mode = ModeFilter
		ui.filterString = ""
		ui.offsetY = 0
	case ev.Ch == 'C':
		ui.mode = ModeColumnSelect
		ui.offsetX = 0
		ui.colIdx = 0
	case ev.Ch == 'G':
		_, height := termbox.Size()
		ui.offsetY = len(ui.rows) - (height - 3)
	case ev.Ch == 'g':
		ui.offsetY = 0
	case ev.Ch == 'Z':
		ui.zebraStripe = !ui.zebraStripe
	case ev.Ch == 'X':
		for i, _ := range ui.columnOpts {
			ui.columnOpts[i].expanded = !globalExpanded
			// FIXME: Possibly not the best behavior
			ui.columnOpts[i].collapsed = false
		}
		globalExpanded = !globalExpanded

	case ui.mode == ModeDefault && ev.Ch == 'q':
		panic("TODO: real exit")
	}
}
<commit_msg>Fix pinned column selection behavior<commit_after>package main

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/nsf/termbox-go"
)

const MAX_CELL_WIDTH = 20
const HILITE_FG = termbox.ColorBlack | termbox.AttrBold
const HILITE_BG = termbox.ColorWhite

type inputMode int

const (
	ModeDefault = iota
	ModeFilter
	ModeColumnSelect
	ModeRowSelect
)

// It is so dumb that go doesn't have this
func clamp(val, lo, hi int) int {
	if val <= lo {
		return lo
	} else if val >= hi {
		return hi
	}

	return val
}

var pinnedBounds = 0

func writeString(x, y int, fg, bg termbox.Attribute, msg string) int {
	for _, c := range msg {
		if x >= pinnedBounds {
			termbox.SetCell(x, y, c, fg, bg)
		}
		x += 1
	}
	return x
}

func writeLine(x, y int, fg, bg termbox.Attribute, line string) {
	width, _ := termbox.Size()
	for _, c := range line {
		termbox.SetCell(x, y, c, fg, bg)
		x += 1
	}
	for i := x; i < width; i += 1 {
		termbox.SetCell(x+i, y, ' ', fg, bg)
	}
}

var cellFmtString = "%-" + strconv.Itoa(MAX_CELL_WIDTH) + "s"

func (ui *UI) writeCell(cell string, x, y, index int, fg, bg termbox.Attribute) int {
	colOpts := ui.columnOpts[index]
	lastCol := index == len(ui.columnOpts)-1

	if index == ui.colIdx && ui.mode == ModeColumnSelect {
		fg = HILITE_FG
		bg = HILITE_BG
	}

	if colOpts.collapsed {
		x = writeString(x, y, fg, bg, "…")
	} else if !colOpts.expanded && len(cell) < MAX_CELL_WIDTH {
		padded := fmt.Sprintf(cellFmtString, cell)
		x = writeString(x, y, fg, bg, padded)
	} else if !colOpts.expanded && !lastCol {
		width := clamp(len(cell)-1, 0, MAX_CELL_WIDTH-1)
		x = writeString(x, y, fg, bg, cell[:width])
		x = writeString(x, y, fg, bg, "…")
	} else {
		writeString(x, y, fg, bg, cell)
		x += colOpts.width
	}

	// Draw separator if this isn't the last element
	if index != len(ui.columns)-1 {
		x = writeString(x, y, termbox.ColorRed, termbox.ColorDefault, " │ ")
	}

	return x
}

func (ui *UI) writePinned(y int, fg, bg termbox.Attribute, row []string) int {
	// ignore our view offsets
	pinnedBounds = 0

	for i, cell := range row {
		colOpts := ui.columnOpts[i]

		if colOpts.pinned {
			pinnedBounds = ui.writeCell(cell, pinnedBounds, y, i, fg, bg)
		}
	}

	return pinnedBounds
}

func (ui *UI) writeColumns(x, y int) {
	var fg, bg termbox.Attribute

	x += ui.writePinned(y, termbox.ColorWhite, termbox.ColorDefault, ui.columns)

	for i, col := range ui.columns {
		colOpts := ui.columnOpts[i]

		fg = termbox.ColorBlack | termbox.AttrBold
		bg = termbox.ColorWhite

		if !colOpts.pinned {
			x = ui.writeCell(col, x, y, i, fg, bg)
		}
	}

	endOfLinePosition = x
}

func (ui *UI) writeRow(x, y int, row []string) {
	fg := termbox.ColorDefault

	if ui.zebraStripe && y%2 == 0 {
		fg = termbox.ColorMagenta
	}

	x += ui.writePinned(y, termbox.ColorCyan, termbox.ColorBlack, row)

	for i, _ := range ui.columns {
		colOpts := ui.columnOpts[i]

		if !colOpts.pinned {
			x = ui.writeCell(row[i], x, y, i, fg, termbox.ColorDefault)
		}
	}
}

type columnOptions struct {
	expanded  bool
	collapsed bool
	pinned    bool
	width     int
}

type UI struct {
	mode             inputMode
	rowIdx, colIdx   int // Selection control
	offsetX, offsetY int // Pan control
	filterString     string
	zebraStripe      bool
	columnOpts       []columnOptions
	columns          []string
	rows             [][]string
	width            int
}

func NewUi(data TabularData) UI {
	colOpts := make([]columnOptions, len(data.Columns))
	columns := make([]string, len(data.Columns))

	for i, col := range data.Columns {
		columns[i] = col.Name
		colOpts[i] = columnOptions{
			expanded:  col.Width < MAX_CELL_WIDTH,
			collapsed: false,
			pinned:    false,
			width:     col.Width,
		}
	}

	return UI{
		offsetX:     0,
		offsetY:     0,
		mode:        ModeDefault,
		colIdx:      -1,
		columnOpts:  colOpts,
		rows:        data.Rows,
		columns:     columns,
		width:       data.Width,
		zebraStripe: false,
	}
}

func (ui *UI) Init() error {
	if err := termbox.Init(); err != nil {
		return err
	}

	termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse)

	return nil
}

func (ui *UI) Loop() {
	defer termbox.Close()

	ui.repaint()

eventloop:
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			if ev.Key == termbox.KeyCtrlC {
				break eventloop
			}

			switch ui.mode {
			case ModeFilter:
				ui.handleKeyFilter(ev)
			case ModeColumnSelect:
				ui.handleKeyColumnSelect(ev)
			default:
				ui.handleKeyDefault(ev)
			}
		}

		ui.repaint()
	}
}

// Return indices of rows to display
func (ui *UI) filterRows(num int) []int {
	rows := make([]int, 0, num)

	// fast pass
	if ui.filterString == "" {
		for i := 0; i < num; i += 1 {
			if i+ui.offsetY >= len(ui.rows) {
				break
			}
			rows = append(rows, i+ui.offsetY)
		}
	} else {
		for i := 0; i < num; i += 1 {
			if i+ui.offsetY >= len(ui.rows) {
				break
			}

			for _, col := range ui.rows[i+ui.offsetY] {
				if strings.Contains(col, ui.filterString) {
					rows = append(rows, i+ui.offsetY)
					break
				}
			}
		}
	}

	return rows
}

func (ui *UI) repaint() {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	termbox.HideCursor()
	_, height := termbox.Size()

	const coldef = termbox.ColorDefault

	ui.writeColumns(ui.offsetX+0, 0)

	rowIdx := ui.filterRows(height - 2)

	for i := 0; i < height-2; i += 1 {
		if i < len(rowIdx) {
			ui.writeRow(ui.offsetX+0, i+1, ui.rows[rowIdx[i]])
		} else {
			writeLine(0, i+1, termbox.ColorWhite|termbox.AttrBold, termbox.ColorBlack, "~")
		}
	}

	switch ui.mode {
	case ModeFilter:
		ext := ""
		if len(rowIdx) == height-2 {
			ext = "+"
		}
		line := fmt.Sprintf("FILTER [%d%s matches]: %s", len(rowIdx), ext, ui.filterString)
		writeLine(0, height-1, termbox.ColorWhite|termbox.AttrBold, termbox.ColorDefault, line)
		termbox.SetCursor(len(line), height-1)
	case ModeColumnSelect:
		line := "COLUMN SELECT (^g quit) [" + ui.columns[ui.colIdx] + "]"
		writeLine(0, height-1, termbox.ColorWhite|termbox.AttrBold, termbox.ColorDefault, line)
	default:
		first := 0
		last := 0
		total := len(ui.rows) - 1
		filter := ""

		if len(rowIdx) >= 2 {
			first = rowIdx[0]
			last = rowIdx[len(rowIdx)-1]
		}

		if ui.filterString != "" {
			filter = fmt.Sprintf("[filter: \"%s\"] ", ui.filterString)
		}

		line := fmt.Sprintf("%s[rows %d-%d of %d] :", filter, first, last, total)
		writeLine(0, height-1, termbox.ColorDefault, termbox.ColorDefault, line)
	}

	termbox.Flush()
}

func (ui *UI) handleKeyFilter(ev termbox.Event) {
	// Ch == 0 implies this was a special key
	if ev.Ch == 0 && ev.Key != termbox.KeySpace {
		if ev.Key == termbox.KeyEsc || ev.Key == termbox.KeyCtrlG || ev.Key == termbox.KeyEnter {
			ui.mode = ModeDefault
		} else if ev.Key == termbox.KeyDelete || ev.Key == termbox.KeyBackspace ||
			ev.Key == termbox.KeyBackspace2 {
			if sz := len(ui.filterString); sz > 0 {
				ui.filterString = ui.filterString[:sz-1]
			}
		} else {
			// Fallback to default handling for arrows etc
			ui.handleKeyDefault(ev)
		}
		return
	}

	if ev.Key == termbox.KeySpace {
		ui.filterString += " "
	} else {
		ui.filterString += string(ev.Ch)
	}

	ui.offsetY = 0
}

var globalExpanded = false
var endOfLinePosition = 0

func (ui *UI) findNextColumn(current, direction int) int {
	isPinned := ui.columnOpts[current].pinned

	// if pinned, find the next pinned col, or vice versa for unpinned
	for i := current + direction; i >= 0 && i < len(ui.columns); i += direction {
		if ui.columnOpts[i].pinned == isPinned {
			return i
		}
	}

	// Don't fall off the end
	if isPinned && direction < 0 || !isPinned && direction > 0 {
		return current
	}

	// there are no remaining pinned / unpinned, just find next col
	i := 0
	if direction < 0 {
		i = len(ui.columns) - 1
	}
	for ; i >= 0 && i < len(ui.columns); i += direction {
		if (isPinned && !ui.columnOpts[i].pinned) || (!isPinned && ui.columnOpts[i].pinned) {
			return i
		}
	}

	return current
}

func (ui *UI) handleKeyColumnSelect(ev termbox.Event) {
	switch {
	case ev.Key == termbox.KeyArrowRight:
		next := ui.findNextColumn(ui.colIdx, 1)
		ui.colIdx = clamp(next, 0, len(ui.columns)-1)

	case ev.Key == termbox.KeyArrowLeft:
		next := ui.findNextColumn(ui.colIdx, -1)
		ui.colIdx = clamp(next, 0, len(ui.columns)-1)

	case ev.Ch == 'w':
		ui.columnOpts[ui.colIdx].collapsed = !ui.columnOpts[ui.colIdx].collapsed
	case ev.Ch == 'x':
		ui.columnOpts[ui.colIdx].expanded = !ui.columnOpts[ui.colIdx].expanded
		if ui.columnOpts[ui.colIdx].expanded {
			ui.columnOpts[ui.colIdx].collapsed = false
		}
	case ev.Ch == '.':
		ui.columnOpts[ui.colIdx].pinned = !ui.columnOpts[ui.colIdx].pinned

		if ui.columnOpts[ui.colIdx].pinned {
			ui.columnOpts[ui.colIdx].collapsed = false
		}

	case ev.Key == termbox.KeyCtrlG, ev.Key == termbox.KeyEsc:
		ui.mode = ModeDefault
	default:
		ui.handleKeyDefault(ev)
	}

	// find if we've gone off screen and readjust
	// TODO: this bit is buggy
	cursorPosition := 0
	for i, _ := range ui.columns {
		colOpts := ui.columnOpts[i]

		if i == ui.colIdx {
			break
		}
		//cursorPosition += 3
		if !colOpts.collapsed {
			cursorPosition += colOpts.width
		}
	}

	width, _ := termbox.Size()
	if cursorPosition > width-ui.offsetX || cursorPosition < -ui.offsetX {
		ui.offsetX = -cursorPosition
	}
}

func (ui *UI) handleKeyDefault(ev termbox.Event) {
	switch {
	case ev.Key == termbox.KeyCtrlA:
		ui.offsetX = 0
	case ev.Key == termbox.KeyCtrlE:
		// FIXME: this is buggy
		w, _ := termbox.Size()
		ui.offsetX = -endOfLinePosition + w
	case ev.Key == termbox.KeyArrowRight:
		ui.offsetX = clamp(ui.offsetX-5, -endOfLinePosition, 0)
	case ev.Key == termbox.KeyArrowLeft:
		ui.offsetX = clamp(ui.offsetX+5, -endOfLinePosition, 0)
	case ev.Key == termbox.KeyArrowUp:
		ui.offsetY = clamp(ui.offsetY-1, 0, len(ui.rows))
	case ev.Key == termbox.KeyArrowDown:
		ui.offsetY = clamp(ui.offsetY+1, 0, len(ui.rows))
	case ev.Ch == '/', ev.Key == termbox.KeyCtrlR:
		ui.mode = ModeFilter
		ui.filterString = ""
		ui.offsetY = 0
	case ev.Ch == 'C':
		ui.mode = ModeColumnSelect
		ui.offsetX = 0
		ui.colIdx = 0
	case ev.Ch == 'G':
		_, height := termbox.Size()
		ui.offsetY = len(ui.rows) - (height - 3)
	case ev.Ch == 'g':
		ui.offsetY = 0
	case ev.Ch == 'Z':
		ui.zebraStripe = !ui.zebraStripe
	case ev.Ch == 'X':
		for i, _ := range ui.columnOpts {
			ui.columnOpts[i].expanded = !globalExpanded
			// FIXME: Possibly not the best behavior
			ui.columnOpts[i].collapsed = false
		}
		globalExpanded = !globalExpanded

	case ui.mode == ModeDefault && ev.Ch == 'q':
		panic("TODO: real exit")
	}
}
<|endoftext|>


<commit_before><commit_msg>add nats connection error logging<commit_after><|endoftext|>


<commit_before><commit_msg>[GC] Fix werf-host-cleanup default percentage and margin values<commit_after><|endoftext|>


<commit_before>// Copyright 2020 Authors of Cilium
//
// 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 azure

import (
	"context"
	"fmt"

	operatorMetrics "github.com/cilium/cilium/operator/metrics"
	operatorOption "github.com/cilium/cilium/operator/option"
	apiMetrics "github.com/cilium/cilium/pkg/api/metrics"
	azureAPI "github.com/cilium/cilium/pkg/azure/api"
	azureIPAM "github.com/cilium/cilium/pkg/azure/ipam"
	"github.com/cilium/cilium/pkg/ipam"
	"github.com/cilium/cilium/pkg/ipam/allocator"
	ipamMetrics "github.com/cilium/cilium/pkg/ipam/metrics"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"

	"github.com/pkg/errors"
)

var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "ipam-allocator-azure")

// AllocatorAzure is an implementation of IPAM allocator interface for Azure
type AllocatorAzure struct{}

// Init in Azure implementation doesn't need to do anything
func (*AllocatorAzure) Init() error { return nil }

// Start kicks of the Azure IP allocation
func (*AllocatorAzure) Start(getterUpdater ipam.CiliumNodeGetterUpdater) (allocator.NodeEventHandler, error) {

	var (
		azMetrics azureAPI.MetricsAPI
		iMetrics  ipam.MetricsAPI
	)

	log.Info("Starting Azure IP allocator...")

	subscriptionID := operatorOption.Config.AzureSubscriptionID
	if subscriptionID == "" {
		log.Debug("SubscriptionID was not specified via CLI, retrieving it via Azure IMS")
		subID, err := azureAPI.GetSubscriptionID(context.TODO())
		if err != nil {
			return nil, errors.Wrap(err, "Azure subscription ID was not specified via CLI and retrieving it from the Azure IMS was not possible")
		}
		subscriptionID = subID
		log.WithField("subscriptionID", subscriptionID).Debug("Detected subscriptionID via Azure IMS")
	}

	resourceGroupName := operatorOption.Config.AzureResourceGroup
	if resourceGroupName == "" {
		log.Debug("ResourceGroupName was not specified via CLI, retrieving it via Azure IMS")
		rgName, err := azureAPI.GetResourceGroupName(context.TODO())
		if err != nil {
			return nil, errors.Wrap(err, "Azure resource group name was not specified via CLI and retrieving it from the Azure IMS was not possible")
		}
		resourceGroupName = rgName
		log.WithField("resourceGroupName", resourceGroupName).Debug("Detected resource group name via Azure IMS")
	}

	if operatorOption.Config.EnableMetrics {
		azMetrics = apiMetrics.NewPrometheusMetrics(operatorMetrics.Namespace, "azure", operatorMetrics.Registry)
		iMetrics = ipamMetrics.NewPrometheusMetrics(operatorMetrics.Namespace, operatorMetrics.Registry)
	} else {
		azMetrics = &apiMetrics.NoOpMetrics{}
		iMetrics = &ipamMetrics.NoOpMetrics{}
	}

	azureClient, err := azureAPI.NewClient(subscriptionID, resourceGroupName, azMetrics, operatorOption.Config.IPAMAPIQPSLimit, operatorOption.Config.IPAMAPIBurst)
	if err != nil {
		return nil, fmt.Errorf("unable to create Azure client: %w", err)
	}
	instances := azureIPAM.NewInstancesManager(azureClient)
	nodeManager, err := ipam.NewNodeManager(instances, getterUpdater, iMetrics, operatorOption.Config.ParallelAllocWorkers, false)
	if err != nil {
		return nil, fmt.Errorf("unable to initialize Azure node manager: %w", err)
	}

	if err := nodeManager.Start(context.TODO()); err != nil {
		return nil, err
	}

	return nodeManager, nil
}
<commit_msg>ipam: use fmt.Errorf instead of github.com/pkg/errors<commit_after>// Copyright 2020 Authors of Cilium
//
// 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 azure

import (
	"context"
	"fmt"

	operatorMetrics "github.com/cilium/cilium/operator/metrics"
	operatorOption "github.com/cilium/cilium/operator/option"
	apiMetrics "github.com/cilium/cilium/pkg/api/metrics"
	azureAPI "github.com/cilium/cilium/pkg/azure/api"
	azureIPAM "github.com/cilium/cilium/pkg/azure/ipam"
	"github.com/cilium/cilium/pkg/ipam"
	"github.com/cilium/cilium/pkg/ipam/allocator"
	ipamMetrics "github.com/cilium/cilium/pkg/ipam/metrics"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
)

var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "ipam-allocator-azure")

// AllocatorAzure is an implementation of IPAM allocator interface for Azure
type AllocatorAzure struct{}

// Init in Azure implementation doesn't need to do anything
func (*AllocatorAzure) Init() error { return nil }

// Start kicks of the Azure IP allocation
func (*AllocatorAzure) Start(getterUpdater ipam.CiliumNodeGetterUpdater) (allocator.NodeEventHandler, error) {

	var (
		azMetrics azureAPI.MetricsAPI
		iMetrics  ipam.MetricsAPI
	)

	log.Info("Starting Azure IP allocator...")

	subscriptionID := operatorOption.Config.AzureSubscriptionID
	if subscriptionID == "" {
		log.Debug("SubscriptionID was not specified via CLI, retrieving it via Azure IMS")
		subID, err := azureAPI.GetSubscriptionID(context.TODO())
		if err != nil {
			return nil, fmt.Errorf("Azure subscription ID was not specified via CLI and retrieving it from the Azure IMS was not possible: %w", err)
		}
		subscriptionID = subID
		log.WithField("subscriptionID", subscriptionID).Debug("Detected subscriptionID via Azure IMS")
	}

	resourceGroupName := operatorOption.Config.AzureResourceGroup
	if resourceGroupName == "" {
		log.Debug("ResourceGroupName was not specified via CLI, retrieving it via Azure IMS")
		rgName, err := azureAPI.GetResourceGroupName(context.TODO())
		if err != nil {
			return nil, fmt.Errorf("Azure resource group name was not specified via CLI and retrieving it from the Azure IMS was not possible: %w", err)
		}
		resourceGroupName = rgName
		log.WithField("resourceGroupName", resourceGroupName).Debug("Detected resource group name via Azure IMS")
	}

	if operatorOption.Config.EnableMetrics {
		azMetrics = apiMetrics.NewPrometheusMetrics(operatorMetrics.Namespace, "azure", operatorMetrics.Registry)
		iMetrics = ipamMetrics.NewPrometheusMetrics(operatorMetrics.Namespace, operatorMetrics.Registry)
	} else {
		azMetrics = &apiMetrics.NoOpMetrics{}
		iMetrics = &ipamMetrics.NoOpMetrics{}
	}

	azureClient, err := azureAPI.NewClient(subscriptionID, resourceGroupName, azMetrics, operatorOption.Config.IPAMAPIQPSLimit, operatorOption.Config.IPAMAPIBurst)
	if err != nil {
		return nil, fmt.Errorf("unable to create Azure client: %w", err)
	}
	instances := azureIPAM.NewInstancesManager(azureClient)
	nodeManager, err := ipam.NewNodeManager(instances, getterUpdater, iMetrics, operatorOption.Config.ParallelAllocWorkers, false)
	if err != nil {
		return nil, fmt.Errorf("unable to initialize Azure node manager: %w", err)
	}

	if err := nodeManager.Start(context.TODO()); err != nil {
		return nil, err
	}

	return nodeManager, nil
}
<|endoftext|>


<commit_before>package yang

import (
	"fmt"
	"os"
	"strings"

	"github.com/openconfig/goyang/pkg/yang"
	"github.com/oshothebig/pbast"
)

var builtinMap = map[yang.TypeKind]pbast.Type{
	yang.Yint8:   pbast.Int32,
	yang.Yint16:  pbast.Int32,
	yang.Yint32:  pbast.Int32,
	yang.Yint64:  pbast.Int64,
	yang.Yuint8:  pbast.UInt32,
	yang.Yuint16: pbast.UInt32,
	yang.Yuint32: pbast.UInt32,
	yang.Yuint64: pbast.UInt64,
	yang.Ystring: pbast.String,
	yang.Ybool:   pbast.Bool,
	yang.Ybinary: pbast.Bytes,
}

var builtinTypes = map[string]struct{}{
	"int8":                struct{}{},
	"int16":               struct{}{},
	"int32":               struct{}{},
	"int64":               struct{}{},
	"uint8":               struct{}{},
	"uint16":              struct{}{},
	"unit32":              struct{}{},
	"uint64":              struct{}{},
	"string":              struct{}{},
	"boolean":             struct{}{},
	"enumeration":         struct{}{},
	"bits":                struct{}{},
	"binary":              struct{}{},
	"leafref":             struct{}{},
	"identityref":         struct{}{},
	"empty":               struct{}{},
	"union":               struct{}{},
	"instance-identifier": struct{}{},
}

type transformer struct {
	topScope  *scope
	decimal64 *pbast.Message
}

// e must be YANG module
func Transform(e *yang.Entry) *pbast.File {
	if _, ok := e.Node.(*yang.Module); !ok {
		return nil
	}

	t := &transformer{
		topScope: newScope(),
	}

	return t.module(entry{e})
}

func (t *transformer) declare(m *pbast.Message) {
	if m == nil {
		return
	}
	if err := t.topScope.addType(m); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}

func (t *transformer) module(e entry) *pbast.File {
	namespace := e.Namespace().Name
	f := pbast.NewFile(pbast.NewPackageWithElements(guessElements(namespace)))

	f.Comment = t.moduleComment(e)

	root := t.buildMessage("Root", e)
	// Clear Root messgage comment because it overlaps with
	// the file level comment being generated from module description too
	root.Comment = nil
	// Child nodes are enclosed with Root message
	f.AddMessage(root)

	// RPCs
	s := t.rpcs(e)
	f.AddService(s)

	// Notifications
	n := t.notifications(e)
	f.AddService(n)

	for _, m := range t.topScope.allTypes() {
		f.AddType(m)
	}
	f.AddMessage(t.decimal64)

	return f
}

func (t *transformer) moduleComment(e entry) pbast.Comment {
	description := t.description(e)
	namespace := t.namespace(e)
	revisions := t.revisions(e)
	reference := t.reference(e)

	var comment []string
	comment = append(comment, description...)
	comment = append(comment, namespace...)
	comment = append(comment, revisions...)
	comment = append(comment, reference...)

	return comment
}

func (t *transformer) genericComments(e entry) pbast.Comment {
	description := t.description(e)
	reference := t.reference(e)

	comments := append(description, reference...)
	return comments
}

func (t *transformer) description(e entry) pbast.Comment {
	description := e.Description
	if e.Description == "" {
		return nil
	}

	lines := strings.Split(strings.TrimRight(description, "\n "), "\n")

	ret := make([]string, 0, len(lines)+1)
	ret = append(ret, "Description:")
	ret = append(ret, lines...)
	return ret
}

func (t *transformer) revisions(e entry) pbast.Comment {
	var lines []string
	if v := e.Extra["revision"]; len(v) > 0 {
		for _, rev := range v[0].([]*yang.Revision) {
			lines = append(lines, "Revision: "+rev.Name)
		}
	}

	return lines
}

func (t *transformer) namespace(e entry) pbast.Comment {
	namespace := e.Namespace().Name
	if namespace == "" {
		return nil
	}

	return []string{"Namespace: " + namespace}
}

func (t *transformer) reference(e entry) pbast.Comment {
	v := e.Extra["reference"]
	if len(v) == 0 {
		return nil
	}

	ref := v[0].(*yang.Value)
	if ref == nil {
		return nil
	}
	if ref.Name == "" {
		return nil
	}

	lines := strings.Split(strings.TrimRight(ref.Name, "\n "), "\n")

	ret := make([]string, 0, len(lines)+1)
	ret = append(ret, "Reference:")
	ret = append(ret, lines...)
	return ret
}

func (t *transformer) rpcs(e entry) *pbast.Service {
	rpcs := e.rpcs()
	if len(rpcs) == 0 {
		return nil
	}

	s := pbast.NewService(CamelCase(e.Name))
	for _, rpc := range rpcs {
		r := t.rpc(rpc)
		s.AddRPC(r)
	}

	return s
}

func (t *transformer) rpc(e entry) *pbast.RPC {
	method := CamelCase(e.Name)
	in := method + "Request"
	out := method + "Response"

	rpc := pbast.NewRPC(
		method,
		pbast.NewReturnType(in),
		pbast.NewReturnType(out),
	)
	rpc.Comment = t.genericComments(e)

	t.declare(t.buildMessage(in, entry{e.RPC.Input}))
	t.declare(t.buildMessage(out, entry{e.RPC.Output}))

	return rpc
}

func (t *transformer) notifications(e entry) *pbast.Service {
	notifications := e.notifications()
	if len(notifications) == 0 {
		return nil
	}

	s := pbast.NewService(CamelCase(e.Name + "Notification"))
	for _, notification := range notifications {
		n := t.notification(notification)
		n.Comment = t.genericComments(notification)
		s.AddRPC(n)
	}

	return s
}

func (t *transformer) notification(e entry) *pbast.RPC {
	method := CamelCase(e.Name)
	in := method + "NotificationRequest"
	out := method + "NotificationResponse"

	rpc := pbast.NewRPC(method, pbast.NewReturnType(in), pbast.NewReturnType(out))

	// notification statement doesn't have an input parameter equivalent,
	// then empty message is used for input as RPC
	t.declare(pbast.NewMessage(in))
	t.declare(t.buildMessage(out, e))

	return rpc
}

func (t *transformer) buildMessage(name string, e entry) *pbast.Message {
	if e.Entry == nil {
		return nil
	}

	msg := pbast.NewMessage(name)
	scope := newScope()
	msg.Comment = t.genericComments(e)
	for index, child := range e.children() {
		fieldNum := index + 1
		var field *pbast.MessageField
		var inner pbast.Type
		switch {
		// leaf-list case
		case child.Type != nil && child.ListAttr != nil:
			field, inner = t.leaf(child, fieldNum, true)
		// leaf case
		case child.Type != nil:
			field, inner = t.leaf(child, fieldNum, false)
		// list case
		case child.ListAttr != nil:
			inner, field = t.directory(child, fieldNum, true)
		// others might be container case
		default:
			inner, field = t.directory(child, fieldNum, false)
		}
		if err := scope.addType(inner); err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
		msg.AddField(field)
	}

	for _, t := range scope.allTypes() {
		msg.AddType(t)
	}

	return msg
}

func (t *transformer) leaf(e entry, index int, repeated bool) (field *pbast.MessageField, nested pbast.Type) {
	typ := builtinMap[e.Type.Kind]
	// no direct builtin type mapping
	// custom message is built
	if typ == nil {
		name := t.typeName(e)
		switch e.Type.Kind {
		// define at the top level
		case yang.Ydecimal64:
			t.decimal64 = decimal64Message
			typ = decimal64Message
		// define as a nested type
		case yang.Ybits:
			typ = t.customBits(name, e.Type.Bit)
		// define as a nested type
		case yang.Yenum:
			typ = t.customEnum(name, e.Type.Enum)
		// not implemented
		case yang.Yunion, yang.Yempty, yang.Yleafref,
			yang.Yidentityref, yang.YinstanceIdentifier:
			return nil, nil
		}
	}

	name := underscoreCase(e.Name)
	field = &pbast.MessageField{
		Repeated: repeated,
		Type:     typ.TypeName(),
		Name:     name,
		Index:    index,
		Comment:  t.genericComments(e),
	}

	if e.Type.Kind == yang.Ydecimal64 {
		return field, nil
	}

	return field, typ
}

// e must be a leaf entry
func (t *transformer) typeName(e entry) string {
	// if the type name matches one of builtin type names,
	// it means typedef is not used
	if _, ok := builtinTypes[e.Type.Name]; ok {
		return CamelCase(e.Name)
	}

	// if typedef is used, use the type name instead of the leaf node name
	return CamelCase(e.Type.Name)
}

func (t *transformer) customBits(name string, bits *yang.EnumType) *pbast.Message {
	msg := pbast.NewMessage(name)
	for i, n := range bits.Names() {
		v := 1 << uint(bits.Values()[i])
		msg.AddField(pbast.NewMessageField(pbast.Bool, n, v))
	}

	return msg
}

func (t *transformer) customEnum(name string, e *yang.EnumType) *pbast.Enum {
	enum := pbast.NewEnum(name)
	for i, n := range e.Names() {
		v := int(e.Values()[i])
		enum.AddField(pbast.NewEnumField(constantName(n), v))
	}

	return enum
}

func (t *transformer) directory(e entry, index int, repeated bool) (*pbast.Message, *pbast.MessageField) {
	fieldName := underscoreCase(e.Name)
	typeName := CamelCase(e.Name)

	inner := t.buildMessage(typeName, e)
	field := &pbast.MessageField{
		Repeated: repeated,
		Type:     inner.TypeName(),
		Name:     fieldName,
		Index:    index,
		Comment:  t.genericComments(e),
	}

	return inner, field
}
<commit_msg>Remove redundant nil check<commit_after>package yang

import (
	"fmt"
	"os"
	"strings"

	"github.com/openconfig/goyang/pkg/yang"
	"github.com/oshothebig/pbast"
)

var builtinMap = map[yang.TypeKind]pbast.Type{
	yang.Yint8:   pbast.Int32,
	yang.Yint16:  pbast.Int32,
	yang.Yint32:  pbast.Int32,
	yang.Yint64:  pbast.Int64,
	yang.Yuint8:  pbast.UInt32,
	yang.Yuint16: pbast.UInt32,
	yang.Yuint32: pbast.UInt32,
	yang.Yuint64: pbast.UInt64,
	yang.Ystring: pbast.String,
	yang.Ybool:   pbast.Bool,
	yang.Ybinary: pbast.Bytes,
}

var builtinTypes = map[string]struct{}{
	"int8":                struct{}{},
	"int16":               struct{}{},
	"int32":               struct{}{},
	"int64":               struct{}{},
	"uint8":               struct{}{},
	"uint16":              struct{}{},
	"unit32":              struct{}{},
	"uint64":              struct{}{},
	"string":              struct{}{},
	"boolean":             struct{}{},
	"enumeration":         struct{}{},
	"bits":                struct{}{},
	"binary":              struct{}{},
	"leafref":             struct{}{},
	"identityref":         struct{}{},
	"empty":               struct{}{},
	"union":               struct{}{},
	"instance-identifier": struct{}{},
}

type transformer struct {
	topScope  *scope
	decimal64 *pbast.Message
}

// e must be YANG module
func Transform(e *yang.Entry) *pbast.File {
	if _, ok := e.Node.(*yang.Module); !ok {
		return nil
	}

	t := &transformer{
		topScope: newScope(),
	}

	return t.module(entry{e})
}

func (t *transformer) declare(m *pbast.Message) {
	if err := t.topScope.addType(m); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}

func (t *transformer) module(e entry) *pbast.File {
	namespace := e.Namespace().Name
	f := pbast.NewFile(pbast.NewPackageWithElements(guessElements(namespace)))

	f.Comment = t.moduleComment(e)

	root := t.buildMessage("Root", e)
	// Clear Root messgage comment because it overlaps with
	// the file level comment being generated from module description too
	root.Comment = nil
	// Child nodes are enclosed with Root message
	f.AddMessage(root)

	// RPCs
	s := t.rpcs(e)
	f.AddService(s)

	// Notifications
	n := t.notifications(e)
	f.AddService(n)

	for _, m := range t.topScope.allTypes() {
		f.AddType(m)
	}
	f.AddMessage(t.decimal64)

	return f
}

func (t *transformer) moduleComment(e entry) pbast.Comment {
	description := t.description(e)
	namespace := t.namespace(e)
	revisions := t.revisions(e)
	reference := t.reference(e)

	var comment []string
	comment = append(comment, description...)
	comment = append(comment, namespace...)
	comment = append(comment, revisions...)
	comment = append(comment, reference...)

	return comment
}

func (t *transformer) genericComments(e entry) pbast.Comment {
	description := t.description(e)
	reference := t.reference(e)

	comments := append(description, reference...)
	return comments
}

func (t *transformer) description(e entry) pbast.Comment {
	description := e.Description
	if e.Description == "" {
		return nil
	}

	lines := strings.Split(strings.TrimRight(description, "\n "), "\n")

	ret := make([]string, 0, len(lines)+1)
	ret = append(ret, "Description:")
	ret = append(ret, lines...)
	return ret
}

func (t *transformer) revisions(e entry) pbast.Comment {
	var lines []string
	if v := e.Extra["revision"]; len(v) > 0 {
		for _, rev := range v[0].([]*yang.Revision) {
			lines = append(lines, "Revision: "+rev.Name)
		}
	}

	return lines
}

func (t *transformer) namespace(e entry) pbast.Comment {
	namespace := e.Namespace().Name
	if namespace == "" {
		return nil
	}

	return []string{"Namespace: " + namespace}
}

func (t *transformer) reference(e entry) pbast.Comment {
	v := e.Extra["reference"]
	if len(v) == 0 {
		return nil
	}

	ref := v[0].(*yang.Value)
	if ref == nil {
		return nil
	}
	if ref.Name == "" {
		return nil
	}

	lines := strings.Split(strings.TrimRight(ref.Name, "\n "), "\n")

	ret := make([]string, 0, len(lines)+1)
	ret = append(ret, "Reference:")
	ret = append(ret, lines...)
	return ret
}

func (t *transformer) rpcs(e entry) *pbast.Service {
	rpcs := e.rpcs()
	if len(rpcs) == 0 {
		return nil
	}

	s := pbast.NewService(CamelCase(e.Name))
	for _, rpc := range rpcs {
		r := t.rpc(rpc)
		s.AddRPC(r)
	}

	return s
}

func (t *transformer) rpc(e entry) *pbast.RPC {
	method := CamelCase(e.Name)
	in := method + "Request"
	out := method + "Response"

	rpc := pbast.NewRPC(
		method,
		pbast.NewReturnType(in),
		pbast.NewReturnType(out),
	)
	rpc.Comment = t.genericComments(e)

	t.declare(t.buildMessage(in, entry{e.RPC.Input}))
	t.declare(t.buildMessage(out, entry{e.RPC.Output}))

	return rpc
}

func (t *transformer) notifications(e entry) *pbast.Service {
	notifications := e.notifications()
	if len(notifications) == 0 {
		return nil
	}

	s := pbast.NewService(CamelCase(e.Name + "Notification"))
	for _, notification := range notifications {
		n := t.notification(notification)
		n.Comment = t.genericComments(notification)
		s.AddRPC(n)
	}

	return s
}

func (t *transformer) notification(e entry) *pbast.RPC {
	method := CamelCase(e.Name)
	in := method + "NotificationRequest"
	out := method + "NotificationResponse"

	rpc := pbast.NewRPC(method, pbast.NewReturnType(in), pbast.NewReturnType(out))

	// notification statement doesn't have an input parameter equivalent,
	// then empty message is used for input as RPC
	t.declare(pbast.NewMessage(in))
	t.declare(t.buildMessage(out, e))

	return rpc
}

func (t *transformer) buildMessage(name string, e entry) *pbast.Message {
	if e.Entry == nil {
		return nil
	}

	msg := pbast.NewMessage(name)
	scope := newScope()
	msg.Comment = t.genericComments(e)
	for index, child := range e.children() {
		fieldNum := index + 1
		var field *pbast.MessageField
		var inner pbast.Type
		switch {
		// leaf-list case
		case child.Type != nil && child.ListAttr != nil:
			field, inner = t.leaf(child, fieldNum, true)
		// leaf case
		case child.Type != nil:
			field, inner = t.leaf(child, fieldNum, false)
		// list case
		case child.ListAttr != nil:
			inner, field = t.directory(child, fieldNum, true)
		// others might be container case
		default:
			inner, field = t.directory(child, fieldNum, false)
		}
		if err := scope.addType(inner); err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
		msg.AddField(field)
	}

	for _, t := range scope.allTypes() {
		msg.AddType(t)
	}

	return msg
}

func (t *transformer) leaf(e entry, index int, repeated bool) (field *pbast.MessageField, nested pbast.Type) {
	typ := builtinMap[e.Type.Kind]
	// no direct builtin type mapping
	// custom message is built
	if typ == nil {
		name := t.typeName(e)
		switch e.Type.Kind {
		// define at the top level
		case yang.Ydecimal64:
			t.decimal64 = decimal64Message
			typ = decimal64Message
		// define as a nested type
		case yang.Ybits:
			typ = t.customBits(name, e.Type.Bit)
		// define as a nested type
		case yang.Yenum:
			typ = t.customEnum(name, e.Type.Enum)
		// not implemented
		case yang.Yunion, yang.Yempty, yang.Yleafref,
			yang.Yidentityref, yang.YinstanceIdentifier:
			return nil, nil
		}
	}

	name := underscoreCase(e.Name)
	field = &pbast.MessageField{
		Repeated: repeated,
		Type:     typ.TypeName(),
		Name:     name,
		Index:    index,
		Comment:  t.genericComments(e),
	}

	if e.Type.Kind == yang.Ydecimal64 {
		return field, nil
	}

	return field, typ
}

// e must be a leaf entry
func (t *transformer) typeName(e entry) string {
	// if the type name matches one of builtin type names,
	// it means typedef is not used
	if _, ok := builtinTypes[e.Type.Name]; ok {
		return CamelCase(e.Name)
	}

	// if typedef is used, use the type name instead of the leaf node name
	return CamelCase(e.Type.Name)
}

func (t *transformer) customBits(name string, bits *yang.EnumType) *pbast.Message {
	msg := pbast.NewMessage(name)
	for i, n := range bits.Names() {
		v := 1 << uint(bits.Values()[i])
		msg.AddField(pbast.NewMessageField(pbast.Bool, n, v))
	}

	return msg
}

func (t *transformer) customEnum(name string, e *yang.EnumType) *pbast.Enum {
	enum := pbast.NewEnum(name)
	for i, n := range e.Names() {
		v := int(e.Values()[i])
		enum.AddField(pbast.NewEnumField(constantName(n), v))
	}

	return enum
}

func (t *transformer) directory(e entry, index int, repeated bool) (*pbast.Message, *pbast.MessageField) {
	fieldName := underscoreCase(e.Name)
	typeName := CamelCase(e.Name)

	inner := t.buildMessage(typeName, e)
	field := &pbast.MessageField{
		Repeated: repeated,
		Type:     inner.TypeName(),
		Name:     fieldName,
		Index:    index,
		Comment:  t.genericComments(e),
	}

	return inner, field
}
<|endoftext|>


<commit_before>package storage

import (
	"code.google.com/p/go-uuid/uuid"
	"encoding/json"
	"github.com/MerlinDMC/dsapid"
	"io/ioutil"
	"os"
	"path"
	"sort"
)

const (
	defaultManifestFilename string = "manifest.json"
)

type ManifestStorage interface {
	Add(string, *dsapid.ManifestResource)
	Get(string) *dsapid.ManifestResource
	GetOK(string) (*dsapid.ManifestResource, bool)
	List() chan *dsapid.ManifestResource
	Filter(...ManifestFilter) chan *dsapid.ManifestResource
	ManifestPath(*dsapid.ManifestResource) string
	FilePath(*dsapid.ManifestResource, *dsapid.ManifestFileResource) string
}

type filesystemManifestStorage struct {
	basedir string

	manifests map[string]*dsapid.ManifestResource
	byDate    []*dsapid.ManifestResource
}

type ManifestFilter func(*dsapid.ManifestResource) bool

type ManifestsByPublishedAt []*dsapid.ManifestResource

func (t ManifestsByPublishedAt) Len() int      { return len(t) }
func (t ManifestsByPublishedAt) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t ManifestsByPublishedAt) Less(i, j int) bool {
	return t[i].PublishedAt.Unix() > t[j].PublishedAt.Unix()
}

func NewManifestStorage(basedir string) ManifestStorage {
	storage := new(filesystemManifestStorage)

	storage.basedir = basedir

	storage.manifests = make(map[string]*dsapid.ManifestResource)
	storage.byDate = make([]*dsapid.ManifestResource, 0)

	storage.load()

	return storage
}

func (me *filesystemManifestStorage) Add(id string, manifest *dsapid.ManifestResource) {
	os.MkdirAll(path.Join(me.basedir, id), 0770)

	if data, err := json.MarshalIndent(manifest, "", "  "); err == nil {
		err = ioutil.WriteFile(path.Join(me.basedir, id, defaultManifestFilename), data, 0666)
	}

	me.add(id, manifest)
}

func (me *filesystemManifestStorage) Get(id string) *dsapid.ManifestResource {
	return me.manifests[id]
}

func (me *filesystemManifestStorage) GetOK(id string) (*dsapid.ManifestResource, bool) {
	v, ok := me.manifests[id]

	return v, ok
}

func (me *filesystemManifestStorage) List() (c chan *dsapid.ManifestResource) {
	c = make(chan *dsapid.ManifestResource)

	go func() {
		for _, item := range me.byDate {
			c <- item
		}

		close(c)
	}()

	return
}

func (me *filesystemManifestStorage) Filter(flist ...ManifestFilter) (c chan *dsapid.ManifestResource) {
	c = make(chan *dsapid.ManifestResource)

	go func() {
	nextItem:
		for item := range me.List() {
			for _, f := range flist {
				if !f(item) {
					continue nextItem
				}
			}

			c <- item
		}

		close(c)
	}()

	return
}

func (me *filesystemManifestStorage) ManifestPath(manifest *dsapid.ManifestResource) string {
	return path.Join(me.basedir, manifest.Uuid)
}

func (me *filesystemManifestStorage) FilePath(manifest *dsapid.ManifestResource, file *dsapid.ManifestFileResource) string {
	return path.Join(me.basedir, manifest.Uuid, file.Path)
}

func (me *filesystemManifestStorage) add(id string, manifest *dsapid.ManifestResource) {
	me.manifests[id] = manifest
	me.byDate = append(me.byDate, manifest)

	sort.Sort(ManifestsByPublishedAt(me.byDate))
}

func (me *filesystemManifestStorage) load() {
	if items, err := ioutil.ReadDir(me.basedir); err == nil {
		for _, item := range items {
			if item.IsDir() {
				manifestFilename := path.Join(me.basedir, item.Name(), defaultManifestFilename)

				if _, err := os.Stat(manifestFilename); err == nil {
					if data, err := ioutil.ReadFile(manifestFilename); err == nil {
						id, manifest := uuid.Parse(item.Name()), dsapid.ManifestResource{}

						json.Unmarshal(data, &manifest)

						if id.String() == manifest.Uuid {
							me.add(manifest.Uuid, &manifest)
						}
					}
				}
			}
		}
	}
}
<commit_msg>manifests should be updateable and deletable<commit_after>package storage

import (
	"code.google.com/p/go-uuid/uuid"
	"encoding/json"
	"github.com/MerlinDMC/dsapid"
	"io/ioutil"
	"os"
	"path"
	"sort"
)

const (
	defaultManifestFilename string = "manifest.json"
)

type ManifestStorage interface {
	Add(string, *dsapid.ManifestResource) error
	Update(string, *dsapid.ManifestResource) error
	Delete(string)
	Get(string) *dsapid.ManifestResource
	GetOK(string) (*dsapid.ManifestResource, bool)
	List() chan *dsapid.ManifestResource
	Filter(...ManifestFilter) chan *dsapid.ManifestResource
	ManifestPath(*dsapid.ManifestResource) string
	FilePath(*dsapid.ManifestResource, *dsapid.ManifestFileResource) string
}

type filesystemManifestStorage struct {
	basedir string

	manifests map[string]*dsapid.ManifestResource
	byDate    []*dsapid.ManifestResource
}

type ManifestFilter func(*dsapid.ManifestResource) bool

type ManifestsByPublishedAt []*dsapid.ManifestResource

func (t ManifestsByPublishedAt) Len() int      { return len(t) }
func (t ManifestsByPublishedAt) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t ManifestsByPublishedAt) Less(i, j int) bool {
	return t[i].PublishedAt.Unix() > t[j].PublishedAt.Unix()
}

func NewManifestStorage(basedir string) ManifestStorage {
	storage := new(filesystemManifestStorage)

	storage.basedir = basedir

	storage.manifests = make(map[string]*dsapid.ManifestResource)
	storage.byDate = make([]*dsapid.ManifestResource, 0)

	storage.load()

	return storage
}

func (me *filesystemManifestStorage) Add(id string, manifest *dsapid.ManifestResource) error {
	os.MkdirAll(path.Join(me.basedir, id), 0770)

	if data, err := json.MarshalIndent(manifest, "", "  "); err == nil {
		if err = ioutil.WriteFile(path.Join(me.basedir, id, defaultManifestFilename), data, 0666); err != nil {
			return err
		}
	}

	me.add(id, manifest)

	return nil
}

func (me *filesystemManifestStorage) Update(id string, manifest *dsapid.ManifestResource) error {
	if data, err := json.MarshalIndent(manifest, "", "  "); err == nil {
		if err = ioutil.WriteFile(path.Join(me.basedir, id, defaultManifestFilename), data, 0666); err != nil {
			return err
		}
	}

	return nil
}

func (me *filesystemManifestStorage) Delete(id string) {
	os.RemoveAll(path.Join(me.basedir, id))

	me.delete(id)
}

func (me *filesystemManifestStorage) Get(id string) *dsapid.ManifestResource {
	return me.manifests[id]
}

func (me *filesystemManifestStorage) GetOK(id string) (*dsapid.ManifestResource, bool) {
	v, ok := me.manifests[id]

	return v, ok
}

func (me *filesystemManifestStorage) List() (c chan *dsapid.ManifestResource) {
	c = make(chan *dsapid.ManifestResource)

	go func() {
		for _, item := range me.byDate {
			c <- item
		}

		close(c)
	}()

	return
}

func (me *filesystemManifestStorage) Filter(flist ...ManifestFilter) (c chan *dsapid.ManifestResource) {
	c = make(chan *dsapid.ManifestResource)

	go func() {
	nextItem:
		for item := range me.List() {
			for _, f := range flist {
				if !f(item) {
					continue nextItem
				}
			}

			c <- item
		}

		close(c)
	}()

	return
}

func (me *filesystemManifestStorage) ManifestPath(manifest *dsapid.ManifestResource) string {
	return path.Join(me.basedir, manifest.Uuid)
}

func (me *filesystemManifestStorage) FilePath(manifest *dsapid.ManifestResource, file *dsapid.ManifestFileResource) string {
	return path.Join(me.basedir, manifest.Uuid, file.Path)
}

func (me *filesystemManifestStorage) add(id string, manifest *dsapid.ManifestResource) {
	me.manifests[id] = manifest
	me.byDate = append(me.byDate, manifest)

	sort.Sort(ManifestsByPublishedAt(me.byDate))
}

func (me *filesystemManifestStorage) delete(id string) {
	for i, m := range me.byDate {
		if m.Uuid == id {
			me.byDate = append(me.byDate[:i], me.byDate[i+1:]...)

			break
		}
	}

	if _, ok := me.manifests[id]; ok {
		delete(me.manifests, id)
	}
}

func (me *filesystemManifestStorage) load() {
	if items, err := ioutil.ReadDir(me.basedir); err == nil {
		for _, item := range items {
			if item.IsDir() {
				manifestFilename := path.Join(me.basedir, item.Name(), defaultManifestFilename)

				if _, err := os.Stat(manifestFilename); err == nil {
					if data, err := ioutil.ReadFile(manifestFilename); err == nil {
						id, manifest := uuid.Parse(item.Name()), dsapid.ManifestResource{}

						json.Unmarshal(data, &manifest)

						if id.String() == manifest.Uuid {
							me.add(manifest.Uuid, &manifest)
						}
					}
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"

	"gitgud.io/softashell/comfy-translator/translator"
)

const (
	userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
	delay     = time.Second / 2
)

var (
	allStringRegex = regexp.MustCompile("\"(.+?)\",\"(.+?)\",?")
	garbageRegex   = regexp.MustCompile(`\s?_{2,3}(\s\d)?`)
)

type Translate struct {
	client      *http.Client
	lastRequest time.Time
	mutex       *sync.Mutex
}

func New() *Translate {
	log.Info("Starting google translation engine")

	return &Translate{
		client:      &http.Client{Timeout: (10 * time.Second)},
		lastRequest: time.Now(),
		mutex:       &sync.Mutex{},
	}
}

func (t Translate) Name() string {
	return "Google"
}

func (t Translate) Translate(req *translator.Request) (string, error) {
	start := time.Now()

	t.mutex.Lock()
	translator.CheckThrottle(t.lastRequest, delay)
	defer t.mutex.Unlock()

	var URL *url.URL
	URL, err := url.Parse("https://translate.google.com/translate_a/single")

	parameters := url.Values{}
	parameters.Add("client", "gtx")
	parameters.Add("dt", "t")
	parameters.Add("sl", req.From)
	parameters.Add("tl", req.To)
	parameters.Add("ie", "UTF-8")
	parameters.Add("oe", "UTF-8")
	parameters.Add("q", req.Text)

	// /translate_a/single?client=gtx&dt=t&sl=%hs&tl=%hs&ie=UTF-8&oe=UTF-8&q=%s
	URL.RawQuery = parameters.Encode()

	r, err := http.NewRequest("GET", URL.String(), nil)
	if err != nil {
		log.Errorln("Failed to create request", err)
		return "", err
	}

	r.Header.Set("User-Agent", userAgent)

	t.lastRequest = time.Now()

	resp, err := t.client.Do(r)
	if err != nil {
		log.Errorln("Failed to do request", err)
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("%s", resp.Status)
	}

	// [[["It will be saved","助かるわい",,,3]],,"ja"]
	contents, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Error("Failed to read response body", err)
		return "", err
	}

	allStrings := allStringRegex.FindAllStringSubmatch(string(contents), -1)

	if len(allStrings) < 1 {
		return "", fmt.Errorf("Bad response %s", contents)
	}

	var out string
	for _, v := range allStrings {
		if len(v) < 3 {
			continue
		}

		out += v[1]
	}

	// Delete garbage output which often leaves the output empty, fix your shit google tbh
	out2 := garbageRegex.ReplaceAllString(out, "")
	if len(out) < 1 || (len(out2) < len(out)/2) {
		return "", fmt.Errorf("Bad response %q", out)
	}

	out = cleanText(out2)

	log.WithFields(log.Fields{
		"time": time.Since(start),
	}).Debugf("Google: %q", out)

	return out, nil
}

func cleanText(text string) string {
	text = strings.Replace(text, "\\\\", "\\", -1)

	// Replace escaped quotes and newlines
	text = strings.Replace(text, "\\\"", "\"", -1)
	text = strings.Replace(text, "\\n", "\n", -1)

	// Replace raw characters
	text = strings.Replace(text, "\\u0026", "＆", -1)

	return text
}
<commit_msg>Replace \u003c and \u003e with < and ><commit_after>package google

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"

	"gitgud.io/softashell/comfy-translator/translator"
)

const (
	userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
	delay     = time.Second / 2
)

var (
	allStringRegex = regexp.MustCompile("\"(.+?)\",\"(.+?)\",?")
	garbageRegex   = regexp.MustCompile(`\s?_{2,3}(\s\d)?`)
)

type Translate struct {
	client      *http.Client
	lastRequest time.Time
	mutex       *sync.Mutex
}

func New() *Translate {
	log.Info("Starting google translation engine")

	return &Translate{
		client:      &http.Client{Timeout: (10 * time.Second)},
		lastRequest: time.Now(),
		mutex:       &sync.Mutex{},
	}
}

func (t Translate) Name() string {
	return "Google"
}

func (t Translate) Translate(req *translator.Request) (string, error) {
	start := time.Now()

	t.mutex.Lock()
	translator.CheckThrottle(t.lastRequest, delay)
	defer t.mutex.Unlock()

	var URL *url.URL
	URL, err := url.Parse("https://translate.google.com/translate_a/single")

	parameters := url.Values{}
	parameters.Add("client", "gtx")
	parameters.Add("dt", "t")
	parameters.Add("sl", req.From)
	parameters.Add("tl", req.To)
	parameters.Add("ie", "UTF-8")
	parameters.Add("oe", "UTF-8")
	parameters.Add("q", req.Text)

	// /translate_a/single?client=gtx&dt=t&sl=%hs&tl=%hs&ie=UTF-8&oe=UTF-8&q=%s
	URL.RawQuery = parameters.Encode()

	r, err := http.NewRequest("GET", URL.String(), nil)
	if err != nil {
		log.Errorln("Failed to create request", err)
		return "", err
	}

	r.Header.Set("User-Agent", userAgent)

	t.lastRequest = time.Now()

	resp, err := t.client.Do(r)
	if err != nil {
		log.Errorln("Failed to do request", err)
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("%s", resp.Status)
	}

	// [[["It will be saved","助かるわい",,,3]],,"ja"]
	contents, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Error("Failed to read response body", err)
		return "", err
	}

	allStrings := allStringRegex.FindAllStringSubmatch(string(contents), -1)

	if len(allStrings) < 1 {
		return "", fmt.Errorf("Bad response %s", contents)
	}

	var out string
	for _, v := range allStrings {
		if len(v) < 3 {
			continue
		}

		out += v[1]
	}

	// Delete garbage output which often leaves the output empty, fix your shit google tbh
	out2 := garbageRegex.ReplaceAllString(out, "")
	if len(out) < 1 || (len(out2) < len(out)/2) {
		return "", fmt.Errorf("Bad response %q", out)
	}

	out = cleanText(out2)

	log.WithFields(log.Fields{
		"time": time.Since(start),
	}).Debugf("Google: %q", out)

	return out, nil
}

func cleanText(text string) string {
	text = strings.Replace(text, "\\\\", "\\", -1)

	// Replace escaped quotes and newlines
	text = strings.Replace(text, "\\\"", "\"", -1)
	text = strings.Replace(text, "\\n", "\n", -1)

	// Replace raw characters
	text = strings.Replace(text, "\\u0026", "＆", -1)
	text = strings.Replace(text, "\\u003c", "<", -1)
	text = strings.Replace(text, "\\u003e", ">", -1)

	return text
}
<|endoftext|>


<commit_before>package main

import (
	"log"

	"github.com/DexterLB/traytor"
	"github.com/DexterLB/traytor/gui"
)

func testSphere(display *gui.Display) {
	rnd := traytor.NewRandom(42)
	for i := 0; i < 10000; i++ {
		vec := rnd.Vec3Sphere()
		vec.Scale(250)
		vec.Add(traytor.NewVec3(400, 0, 400))
		display.SetPixel(int(vec.X), int(vec.Z), traytor.NewColour32Bit(0x0fff, 0xffff, 0x4000))
	}
}

func testHemi(display *gui.Display) {
	rnd := traytor.NewRandom(42)
	for i := 0; i < 4000; i++ {
		vec := rnd.Vec3Hemi(traytor.NewVec3(0, 0, -1))
		vec.Scale(250)
		vec.Add(traytor.NewVec3(400, 0, 400))
		display.SetPixel(int(vec.X), int(vec.Z), traytor.NewColour32Bit(0x0fff, 0xffff, 0x4000))
	}
}

func testHemiCos(display *gui.Display) {
	rnd := traytor.NewRandom(42)
	for i := 0; i < 4000; i++ {
		vec := rnd.Vec3HemiCos(traytor.NewVec3(0, 0, 1))
		vec.Scale(250)
		vec.Add(traytor.NewVec3(400, 0, 400))
		display.SetPixel(int(vec.X), int(vec.Z), traytor.NewColour32Bit(0xffff, 0x0fff, 0x4000))
	}
}

func main() {
	defer gui.Quit()

	display, err := gui.NewDisplay(800, 800, "shite")
	if err != nil {
		log.Fatal(err)
		return
	}
	defer display.Close()

	testHemi(display)
	testHemiCos(display)

	display.Update()

	display.Loop()
}
<commit_msg>make the random test prettier<commit_after>package main

import (
	"log"
	"time"

	"github.com/DexterLB/traytor"
	"github.com/DexterLB/traytor/gui"
)

func testSphere(display *gui.Display) {
	rnd := traytor.NewRandom(42)
	for i := 0; i < 10000; i++ {
		vec := rnd.Vec3Sphere()
		vec.Scale(350)
		vec.Add(traytor.NewVec3(400, 0, 400))
		display.SetPixel(int(vec.X), int(vec.Z), traytor.NewColour32Bit(0x0fff, 0xffff, 0x4000))
		time.Sleep(100 * time.Microsecond)
		display.Update()
	}
}

func testHemi(display *gui.Display) {
	rnd := traytor.NewRandom(42)
	for i := 0; i < 17000; i++ {
		vec := rnd.Vec3Hemi(traytor.NewVec3(0, 0, -1))
		vec.Scale(350)
		vec.Add(traytor.NewVec3(400, 0, 400))
		display.SetPixel(int(vec.X), int(vec.Z), traytor.NewColour32Bit(0x0fff, 0xffff, 0x4000))

		vecCos := rnd.Vec3HemiCos(traytor.NewVec3(0, 0, 1))
		vecCos.Scale(350)
		vecCos.Add(traytor.NewVec3(400, 0, 400))
		display.SetPixel(int(vecCos.X), int(vecCos.Z), traytor.NewColour32Bit(0xffff, 0x0fff, 0x4000))

		time.Sleep(100 * time.Microsecond)
		display.Update()
	}
}

func main() {
	defer gui.Quit()

	display, err := gui.NewDisplay(800, 800, "shite")
	if err != nil {
		log.Fatal(err)
		return
	}
	defer display.Close()

	testHemi(display)

	display.Update()

	display.Loop()
}
<|endoftext|>


<commit_before>/* linked_list.go : Implement linked list with too many memory allocations
Author: James Fairbanks
Date: 2012-09-02
Liscence: BSD
*/

package list

import (
	"fmt"
	"strings"

//    "github.com/jpfairbanks/timing"
//    "math/rand"
)

//Dtype: the data type of the arrays
type Dtype int64

var NULL *Node

//NULL := 0

//Node: Struct containing a datum and a pointer to the next Node.
type Node struct {
	Datum Dtype
	Next  *Node
}

//List: Struct for containing the head of the list.
type List struct {
	Head *Node
}

//New: Make a new list of Nodes.
func New() List {
	var ell List
	head := &Node{Datum: 0, Next: NULL}
	ell.Head = head
	return ell
}

//Insert: Insert a Node into the list.
func (ell *List) Insert(pos int, element Dtype) {
	var prev *Node
	node := ell.Head
	for i := 0; i < pos; i++ {
		//fmt.Println("getting the next")
		if i == pos-1 {
			prev = node
		}
		node = node.Next
	}
	//fmt.Printf("found pos node: %v\n", node)
	//fmt.Printf("found prev: %v\n", prev)
	newNode := &Node{Datum: element, Next: node}
	//fmt.Printf("made newNode: %v\n", newNode)
	if pos > 0 {
		prev.Next = newNode
	} else {
		ell.Head = newNode
	}
	//fmt.Println("did previous")
	//fmt.Println(node.Next)
}

//Remove: Remove a Node into the list.
func (ell *List) Remove(pos int) Dtype {
	node := ell.Head
	for i := 0; i < pos; i++ {
		node = node.Next
	}
	next := node.Next
	node.Next = node.Next.Next
	return next.Datum
}

//String: print  list by printing each element on a line
func (ell *List) String() string {
	var curr *Node
	curr = ell.Head
	var repr map[int]string
	repr = make(map[int]string)
	var sterm string
	var i int
	for curr != nil {
		sterm = fmt.Sprintf("%d %v %v",
			i, curr.Datum, curr.Next)
		//fmt.Println(sterm)
		repr[i] = sterm
		i++
		curr = curr.Next
	}
	ordered := make([]string, len(repr))
	for j := 0; j < i; j++ {
		ordered[j] = repr[j]
	}
	var joined string
	joined = strings.Join(ordered, "\n")
	//fmt.Println(joined)
	return joined
}

//Strider: a slice of type dtype with a fixed stride
type Strider interface {
	Walk() Dtype
}
<commit_msg>remove whitespace<commit_after>/* linked_list.go : Implement linked list with too many memory allocations
Author: James Fairbanks
Date: 2012-09-02
Liscence: BSD
*/

package list

import (
	"fmt"
	"strings"
//    "github.com/jpfairbanks/timing"
//    "math/rand"
)

//Dtype: the data type of the arrays
type Dtype int64

var NULL *Node

//NULL := 0

//Node: Struct containing a datum and a pointer to the next Node.
type Node struct {
	Datum Dtype
	Next  *Node
}

//List: Struct for containing the head of the list.
type List struct {
	Head *Node
}

//New: Make a new list of Nodes.
func New() List {
	var ell List
	head := &Node{Datum: 0, Next: NULL}
	ell.Head = head
	return ell
}

//Insert: Insert a Node into the list.
func (ell *List) Insert(pos int, element Dtype) {
	var prev *Node
	node := ell.Head
	for i := 0; i < pos; i++ {
		//fmt.Println("getting the next")
		if i == pos-1 {
			prev = node
		}
		node = node.Next
	}
	//fmt.Printf("found pos node: %v\n", node)
	//fmt.Printf("found prev: %v\n", prev)
	newNode := &Node{Datum: element, Next: node}
	//fmt.Printf("made newNode: %v\n", newNode)
	if pos > 0 {
		prev.Next = newNode
	} else {
		ell.Head = newNode
	}
	//fmt.Println("did previous")
	//fmt.Println(node.Next)
}

//Remove: Remove a Node into the list.
func (ell *List) Remove(pos int) Dtype {
	node := ell.Head
	for i := 0; i < pos; i++ {
		node = node.Next
	}
	next := node.Next
	node.Next = node.Next.Next
	return next.Datum
}

//String: print  list by printing each element on a line
func (ell *List) String() string {
	var curr *Node
	curr = ell.Head
	var repr map[int]string
	repr = make(map[int]string)
	var sterm string
	var i int
	for curr != nil {
		sterm = fmt.Sprintf("%d %v %v",
			i, curr.Datum, curr.Next)
		//fmt.Println(sterm)
		repr[i] = sterm
		i++
		curr = curr.Next
	}
	ordered := make([]string, len(repr))
	for j := 0; j < i; j++ {
		ordered[j] = repr[j]
	}
	var joined string
	joined = strings.Join(ordered, "\n")
	//fmt.Println(joined)
	return joined
}

//Strider: a slice of type dtype with a fixed stride
type Strider interface {
	Walk() Dtype
}
<|endoftext|>


<commit_before>// Copyright 2016 The Prometheus 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 stackdriver

import (
	"context"
	"crypto/tls"
	"fmt"
	"strconv"
	"sync"
	"time"

	monitoring "google.golang.org/genproto/googleapis/monitoring/v3"
	"google.golang.org/grpc"
	"google.golang.org/grpc/balancer/roundrobin"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/oauth"
	"google.golang.org/grpc/status"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/common/model"
	"github.com/prometheus/common/version"

	config_util "github.com/prometheus/common/config"
)

const (
	// TODO(jkohen): Use a custom prefix specific to Prometheus.
	metricsPrefix             = "custom.googleapis.com"
	maxTimeseriesesPerRequest = 200
	MonitoringWriteScope      = "https://www.googleapis.com/auth/monitoring.write"
)

// Client allows reading and writing from/to a remote gRPC endpoint. The
// implementation may hit a single backend, so the application should create a
// number of these clients.
type Client struct {
	index     int // Used to differentiate clients in metrics.
	logger    log.Logger
	projectId string
	url       *config_util.URL
	timeout   time.Duration

	conn *grpc.ClientConn
}

// ClientConfig configures a Client.
type ClientConfig struct {
	Logger    log.Logger
	ProjectId string // The Stackdriver project id in "projects/name-or-number" format.
	URL       *config_util.URL
	Timeout   model.Duration
}

// NewClient creates a new Client.
func NewClient(index int, conf *ClientConfig) *Client {
	logger := conf.Logger
	if logger == nil {
		logger = log.NewNopLogger()
	}
	return &Client{
		index:     index,
		logger:    logger,
		projectId: conf.ProjectId,
		url:       conf.URL,
		timeout:   time.Duration(conf.Timeout),
	}
}

type recoverableError struct {
	error
}

// version.* is populated for 'promu' builds, so this will look broken in unit tests.
var userAgent = fmt.Sprintf("StackdriverPrometheus/%s", version.Version)

func (c *Client) getConnection(ctx context.Context) (*grpc.ClientConn, error) {
	if c.conn != nil {
		return c.conn, nil
	}

	useAuth, err := strconv.ParseBool(c.url.Query().Get("auth"))
	if err != nil {
		useAuth = true // Default to auth enabled.
	}
	level.Debug(c.logger).Log(
		"msg", "is auth enabled",
		"auth", useAuth,
		"url", c.url.String())
	// Google APIs currently return a single IP for the whole service.  gRPC
	// client-side load-balancing won't spread the load across backends
	// while that's true, but it also doesn't hurt.
	dopts := []grpc.DialOption{
		grpc.WithBalancerName(roundrobin.Name),
		grpc.WithBlock(), // Wait for the connection to be established before using it.
		grpc.WithUserAgent(userAgent),
	}
	if useAuth {
		rpcCreds, err := oauth.NewApplicationDefault(context.Background(), MonitoringWriteScope)
		if err != nil {
			return nil, err
		}
		tlsCreds := credentials.NewTLS(&tls.Config{})
		dopts = append(dopts,
			grpc.WithTransportCredentials(tlsCreds),
			grpc.WithPerRPCCredentials(rpcCreds))
	} else {
		dopts = append(dopts, grpc.WithInsecure())
	}
	address := c.url.Hostname()
	if len(c.url.Port()) > 0 {
		address = fmt.Sprintf("%s:%s", address, c.url.Port())
	}
	conn, err := grpc.DialContext(ctx, address, dopts...)
	c.conn = conn
	return conn, err
}

// Store sends a batch of samples to the HTTP endpoint.
func (c *Client) Store(req *monitoring.CreateTimeSeriesRequest) error {
	tss := req.TimeSeries
	if len(tss) == 0 {
		// Nothing to do, return silently.
		return nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	conn, err := c.getConnection(ctx)
	if err != nil {
		return err
	}

	level.Debug(c.logger).Log("msg", "sending request to Stackdriver")
	service := monitoring.NewMetricServiceClient(conn)

	errors := make(chan error, len(tss)/maxTimeseriesesPerRequest+1)
	var wg sync.WaitGroup
	for i := 0; i < len(tss); i += maxTimeseriesesPerRequest {
		end := i + maxTimeseriesesPerRequest
		if end > len(tss) {
			end = len(tss)
		}
		wg.Add(1)
		go func(begin int, end int) {
			defer wg.Done()
			req_copy := &monitoring.CreateTimeSeriesRequest{
				Name:       c.projectId,
				TimeSeries: req.TimeSeries[begin:end],
			}
			_, err := service.CreateTimeSeries(ctx, req_copy)
			if err != nil {
				level.Debug(c.logger).Log(
					"msg", "Partial failure calling CreateTimeSeries",
					"err", err,
					"req", req_copy.String())
				status, ok := status.FromError(err)
				if !ok {
					level.Warn(c.logger).Log("msg", "Unexpected error message type from Monitoring API", "err", err)
					errors <- err
					return
				}
				if status.Code() == codes.Unavailable {
					errors <- recoverableError{err}
				} else {
					errors <- err
				}
			}
		}(i, end)
	}
	wg.Wait()
	close(errors)
	if err, ok := <-errors; ok {
		return err
	}
	return nil
}

// Name identifies the client.
func (c Client) Name() string {
	return fmt.Sprintf("%d:%s", c.index, c.url)
}

func (c Client) Close() error {
	if c.conn == nil {
		return nil
	}
	return c.conn.Close()
}
<commit_msg>Changed prefix from 'custom.googleapis.com' to 'external.googleapis.com/prometheus'. This will break this program until the prefix is supported in the backend, coming ASAP.<commit_after>// Copyright 2016 The Prometheus 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 stackdriver

import (
	"context"
	"crypto/tls"
	"fmt"
	"strconv"
	"sync"
	"time"

	monitoring "google.golang.org/genproto/googleapis/monitoring/v3"
	"google.golang.org/grpc"
	"google.golang.org/grpc/balancer/roundrobin"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/oauth"
	"google.golang.org/grpc/status"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/common/model"
	"github.com/prometheus/common/version"

	config_util "github.com/prometheus/common/config"
)

const (
	metricsPrefix             = "external.googleapis.com/prometheus"
	maxTimeseriesesPerRequest = 200
	MonitoringWriteScope      = "https://www.googleapis.com/auth/monitoring.write"
)

// Client allows reading and writing from/to a remote gRPC endpoint. The
// implementation may hit a single backend, so the application should create a
// number of these clients.
type Client struct {
	index     int // Used to differentiate clients in metrics.
	logger    log.Logger
	projectId string
	url       *config_util.URL
	timeout   time.Duration

	conn *grpc.ClientConn
}

// ClientConfig configures a Client.
type ClientConfig struct {
	Logger    log.Logger
	ProjectId string // The Stackdriver project id in "projects/name-or-number" format.
	URL       *config_util.URL
	Timeout   model.Duration
}

// NewClient creates a new Client.
func NewClient(index int, conf *ClientConfig) *Client {
	logger := conf.Logger
	if logger == nil {
		logger = log.NewNopLogger()
	}
	return &Client{
		index:     index,
		logger:    logger,
		projectId: conf.ProjectId,
		url:       conf.URL,
		timeout:   time.Duration(conf.Timeout),
	}
}

type recoverableError struct {
	error
}

// version.* is populated for 'promu' builds, so this will look broken in unit tests.
var userAgent = fmt.Sprintf("StackdriverPrometheus/%s", version.Version)

func (c *Client) getConnection(ctx context.Context) (*grpc.ClientConn, error) {
	if c.conn != nil {
		return c.conn, nil
	}

	useAuth, err := strconv.ParseBool(c.url.Query().Get("auth"))
	if err != nil {
		useAuth = true // Default to auth enabled.
	}
	level.Debug(c.logger).Log(
		"msg", "is auth enabled",
		"auth", useAuth,
		"url", c.url.String())
	// Google APIs currently return a single IP for the whole service.  gRPC
	// client-side load-balancing won't spread the load across backends
	// while that's true, but it also doesn't hurt.
	dopts := []grpc.DialOption{
		grpc.WithBalancerName(roundrobin.Name),
		grpc.WithBlock(), // Wait for the connection to be established before using it.
		grpc.WithUserAgent(userAgent),
	}
	if useAuth {
		rpcCreds, err := oauth.NewApplicationDefault(context.Background(), MonitoringWriteScope)
		if err != nil {
			return nil, err
		}
		tlsCreds := credentials.NewTLS(&tls.Config{})
		dopts = append(dopts,
			grpc.WithTransportCredentials(tlsCreds),
			grpc.WithPerRPCCredentials(rpcCreds))
	} else {
		dopts = append(dopts, grpc.WithInsecure())
	}
	address := c.url.Hostname()
	if len(c.url.Port()) > 0 {
		address = fmt.Sprintf("%s:%s", address, c.url.Port())
	}
	conn, err := grpc.DialContext(ctx, address, dopts...)
	c.conn = conn
	return conn, err
}

// Store sends a batch of samples to the HTTP endpoint.
func (c *Client) Store(req *monitoring.CreateTimeSeriesRequest) error {
	tss := req.TimeSeries
	if len(tss) == 0 {
		// Nothing to do, return silently.
		return nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	conn, err := c.getConnection(ctx)
	if err != nil {
		return err
	}

	level.Debug(c.logger).Log("msg", "sending request to Stackdriver")
	service := monitoring.NewMetricServiceClient(conn)

	errors := make(chan error, len(tss)/maxTimeseriesesPerRequest+1)
	var wg sync.WaitGroup
	for i := 0; i < len(tss); i += maxTimeseriesesPerRequest {
		end := i + maxTimeseriesesPerRequest
		if end > len(tss) {
			end = len(tss)
		}
		wg.Add(1)
		go func(begin int, end int) {
			defer wg.Done()
			req_copy := &monitoring.CreateTimeSeriesRequest{
				Name:       c.projectId,
				TimeSeries: req.TimeSeries[begin:end],
			}
			_, err := service.CreateTimeSeries(ctx, req_copy)
			if err != nil {
				level.Debug(c.logger).Log(
					"msg", "Partial failure calling CreateTimeSeries",
					"err", err,
					"req", req_copy.String())
				status, ok := status.FromError(err)
				if !ok {
					level.Warn(c.logger).Log("msg", "Unexpected error message type from Monitoring API", "err", err)
					errors <- err
					return
				}
				if status.Code() == codes.Unavailable {
					errors <- recoverableError{err}
				} else {
					errors <- err
				}
			}
		}(i, end)
	}
	wg.Wait()
	close(errors)
	if err, ok := <-errors; ok {
		return err
	}
	return nil
}

// Name identifies the client.
func (c Client) Name() string {
	return fmt.Sprintf("%d:%s", c.index, c.url)
}

func (c Client) Close() error {
	if c.conn == nil {
		return nil
	}
	return c.conn.Close()
}
<|endoftext|>