<commit_before>// Package realtime - Fetch realtime stock data info
// 擷取盤中個股、指數即時股價資訊
//
package realtime

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

	"github.com/toomore/gogrs/utils"
)

//STOCKPATH = '/stock/api/getStockInfo.jsp?ex_ch=%(exchange)s_%(no)s.tw_%(date)s&json=1&delay=%(delay)s&_=%(timestamp)s'

type msgArray []map[string]string
type unixMapData map[int64]msgArray

// StockRealTime start with No, Timestamp, Date.
type StockRealTime struct {
	No          string      // 股票代碼
	Date        time.Time   // 擷取時間
	UnixMapData unixMapData // 時間資料暫存
	Exchange    string      // tse, otc
}

// StockBlob return map data.
type StockBlob struct {
	Rtcode    string
	UserDelay int
	Rtmessage string
	Referer   string
	MsgArray  msgArray
	QueryTime map[string]interface{}
}

// URL return realtime url path.
func (stock StockRealTime) URL() string {
	if utils.ExchangeMap[stock.Exchange] {
		return fmt.Sprintf("%s%s", utils.TWSEURL,
			fmt.Sprintf(utils.TWSEREAL,
				stock.Exchange,
				stock.No,
				fmt.Sprintf(
					"%d%02d%02d",
					stock.Date.Year(),
					int(stock.Date.Month()),
					stock.Date.Day(),
				),
				utils.RandInt(),
			))
	}
	return ""
}

// StockInfo is base stock info.
type StockInfo struct {
	Exchange string // tse or otc
	FullName string // Full company name.
	Name     string // Stock name.
	No       string // Stock no
	Ticker   string // Ticker symbol（股票代號）
	Category string // 股票類別代號
}

// Data is realtime return formated data.
type Data struct {
	BestAskPrice   []float64              // 最佳五檔賣出價資訊
	BestBidPrice   []float64              // 最佳五檔買進價資訊
	BestAskVolume  []int64                // 最佳五檔賣出量資訊
	BestBidVolume  []int64                // 最佳五檔買進量資訊
	Open           float64                // 開盤價格
	Highest        float64                // 最高價
	Lowest         float64                // 最低價
	Price          float64                // 該盤成交價格
	LimitUp        float64                // 漲停價
	LimitDown      float64                // 跌停價
	Volume         float64                // 該盤成交量
	VolumeAcc      float64                // 累計成交量
	YesterdayPrice float64                // 昨日收盤價格
	TradeTime      time.Time              // 交易時間
	Info           StockInfo              // 相關資訊
	SysInfo        map[string]interface{} // 系統回傳資訊
}

func (stock *StockRealTime) get() (StockBlob, error) {
	var value StockBlob
	url := stock.URL()
	resp, err := http.Get(url)
	if err != nil {
		return value, fmt.Errorf("Network fail: %s", err)
	}
	defer resp.Body.Close()
	json.NewDecoder(resp.Body).Decode(&value)

	if len(value.MsgArray) != 0 {
		unixTime, _ := strconv.ParseInt(value.MsgArray[0]["tlong"], 10, 64)
		if stock.UnixMapData == nil {
			stock.UnixMapData = make(unixMapData)
		}

		// Should format data.
		stock.UnixMapData[unixTime/1000] = value.MsgArray
		return value, nil
	}
	return value, fmt.Errorf("No Data.")
}

// Get return stock realtime map data.
func (stock *StockRealTime) Get() (Data, error) {
	value, err := stock.get()
	var result Data

	if err == nil && len(value.MsgArray) != 0 {
		aList := strings.Split(value.MsgArray[0]["a"], "_")
		result.BestAskPrice = make([]float64, len(aList)-1)
		for i, v := range aList[:len(aList)-1] {
			result.BestAskPrice[i], _ = strconv.ParseFloat(v, 10)
		}

		bList := strings.Split(value.MsgArray[0]["b"], "_")
		result.BestBidPrice = make([]float64, len(bList)-1)
		for i, v := range bList[:len(bList)-1] {
			result.BestBidPrice[i], _ = strconv.ParseFloat(v, 10)
		}

		fList := strings.Split(value.MsgArray[0]["f"], "_")
		result.BestAskVolume = make([]int64, len(fList)-1)
		for i, v := range fList[:len(fList)-1] {
			result.BestAskVolume[i], _ = strconv.ParseInt(v, 10, 64)
		}

		gList := strings.Split(value.MsgArray[0]["g"], "_")
		result.BestBidVolume = make([]int64, len(gList)-1)
		for i, v := range gList[:len(gList)-1] {
			result.BestBidVolume[i], _ = strconv.ParseInt(v, 10, 64)
		}

		result.Open, _ = strconv.ParseFloat(value.MsgArray[0]["o"], 10)
		result.Highest, _ = strconv.ParseFloat(value.MsgArray[0]["h"], 10)
		result.Lowest, _ = strconv.ParseFloat(value.MsgArray[0]["l"], 10)
		result.Price, _ = strconv.ParseFloat(value.MsgArray[0]["z"], 10)
		result.LimitUp, _ = strconv.ParseFloat(value.MsgArray[0]["u"], 10)
		result.LimitDown, _ = strconv.ParseFloat(value.MsgArray[0]["w"], 10)
		result.Volume, _ = strconv.ParseFloat(value.MsgArray[0]["tv"], 10)
		result.VolumeAcc, _ = strconv.ParseFloat(value.MsgArray[0]["v"], 10)
		result.YesterdayPrice, _ = strconv.ParseFloat(value.MsgArray[0]["y"], 10)
		tlong, _ := strconv.ParseInt(value.MsgArray[0]["tlong"], 10, 64)
		result.TradeTime = time.Unix(tlong/1000, 0)

		result.Info.No = value.MsgArray[0]["c"]
		result.Info.FullName = value.MsgArray[0]["nf"]
		result.Info.Name = value.MsgArray[0]["n"]
		result.Info.Ticker = value.MsgArray[0]["ch"]
		result.Info.Exchange = value.MsgArray[0]["ex"]
		result.Info.Category = value.MsgArray[0]["i"]

		result.SysInfo = make(map[string]interface{})
		result.SysInfo = value.QueryTime
	}
	return result, err
}

// NewTWSE 建立一個上市股票
func NewTWSE(No string, Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       No,
		Date:     Date,
		Exchange: "tse",
	}
}

// NewOTC 建立一個上櫃股票
func NewOTC(No string, Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       No,
		Date:     Date,
		Exchange: "otc",
	}
}

// NewWeight 大盤指數
func NewWeight(Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       "t00",
		Date:     Date,
		Exchange: "tse",
	}
}

// NewOTCI 上櫃指數
func NewOTCI(Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       "o00",
		Date:     Date,
		Exchange: "otc",
	}
}

// NewFRMSA 寶島指數
func NewFRMSA(Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       "FRMSA",
		Date:     Date,
		Exchange: "tse",
	}
}
<commit_msg>Tiny changed.<commit_after>// Package realtime - Fetch realtime stock data info
// 擷取盤中個股、指數即時股價資訊
//
package realtime

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

	"github.com/toomore/gogrs/utils"
)

//STOCKPATH = '/stock/api/getStockInfo.jsp?ex_ch=%(exchange)s_%(no)s.tw_%(date)s&json=1&delay=%(delay)s&_=%(timestamp)s'

type msgArray []map[string]string
type unixMapData map[int64]msgArray

// StockRealTime start with No, Timestamp, Date.
type StockRealTime struct {
	No          string      // 股票代碼
	Date        time.Time   // 擷取時間
	UnixMapData unixMapData // 時間資料暫存
	Exchange    string      // tse, otc
}

// StockBlob return map data.
type StockBlob struct {
	Rtcode    string
	UserDelay int
	Rtmessage string
	Referer   string
	MsgArray  msgArray
	QueryTime map[string]interface{}
}

// URL return realtime url path.
func (stock StockRealTime) URL() string {
	if utils.ExchangeMap[stock.Exchange] {
		return fmt.Sprintf("%s%s", utils.TWSEURL,
			fmt.Sprintf(utils.TWSEREAL,
				stock.Exchange,
				stock.No,
				fmt.Sprintf(
					"%d%02d%02d",
					stock.Date.Year(),
					int(stock.Date.Month()),
					stock.Date.Day(),
				),
				utils.RandInt(),
			))
	}
	return ""
}

// StockInfo is base stock info.
type StockInfo struct {
	Exchange string // tse or otc
	FullName string // Full company name.
	Name     string // Stock name.
	No       string // Stock no
	Ticker   string // Ticker symbol（股票代號）
	Category string // 股票類別代號
}

// Data is realtime return formated data.
type Data struct {
	BestAskPrice   []float64              // 最佳五檔賣出價資訊
	BestBidPrice   []float64              // 最佳五檔買進價資訊
	BestAskVolume  []int64                // 最佳五檔賣出量資訊
	BestBidVolume  []int64                // 最佳五檔買進量資訊
	Open           float64                // 開盤價格
	Highest        float64                // 最高價
	Lowest         float64                // 最低價
	Price          float64                // 該盤成交價格
	LimitUp        float64                // 漲停價
	LimitDown      float64                // 跌停價
	Volume         float64                // 該盤成交量
	VolumeAcc      float64                // 累計成交量
	YesterdayPrice float64                // 昨日收盤價格
	TradeTime      time.Time              // 交易時間
	Info           StockInfo              // 相關資訊
	SysInfo        map[string]interface{} // 系統回傳資訊
}

func (stock *StockRealTime) get() (StockBlob, error) {
	var value StockBlob
	url := stock.URL()
	resp, err := http.Get(url)
	if err != nil {
		return value, fmt.Errorf("Network fail: %s", err)
	}
	defer resp.Body.Close()
	json.NewDecoder(resp.Body).Decode(&value)

	if len(value.MsgArray) != 0 {
		unixTime, _ := strconv.ParseInt(value.MsgArray[0]["tlong"], 10, 64)
		if stock.UnixMapData == nil {
			stock.UnixMapData = make(unixMapData)
		}

		// Should format data.
		stock.UnixMapData[unixTime/1000] = value.MsgArray
		return value, nil
	}
	return value, fmt.Errorf("No Data.")
}

// Get return stock realtime map data.
func (stock *StockRealTime) Get() (Data, error) {
	var (
		err    error
		result Data
		value  StockBlob
	)

	if value, err = stock.get(); err == nil && len(value.MsgArray) != 0 {
		aList := strings.Split(value.MsgArray[0]["a"], "_")
		result.BestAskPrice = make([]float64, len(aList)-1)
		for i, v := range aList[:len(aList)-1] {
			result.BestAskPrice[i], _ = strconv.ParseFloat(v, 10)
		}

		bList := strings.Split(value.MsgArray[0]["b"], "_")
		result.BestBidPrice = make([]float64, len(bList)-1)
		for i, v := range bList[:len(bList)-1] {
			result.BestBidPrice[i], _ = strconv.ParseFloat(v, 10)
		}

		fList := strings.Split(value.MsgArray[0]["f"], "_")
		result.BestAskVolume = make([]int64, len(fList)-1)
		for i, v := range fList[:len(fList)-1] {
			result.BestAskVolume[i], _ = strconv.ParseInt(v, 10, 64)
		}

		gList := strings.Split(value.MsgArray[0]["g"], "_")
		result.BestBidVolume = make([]int64, len(gList)-1)
		for i, v := range gList[:len(gList)-1] {
			result.BestBidVolume[i], _ = strconv.ParseInt(v, 10, 64)
		}

		result.Open, _ = strconv.ParseFloat(value.MsgArray[0]["o"], 10)
		result.Highest, _ = strconv.ParseFloat(value.MsgArray[0]["h"], 10)
		result.Lowest, _ = strconv.ParseFloat(value.MsgArray[0]["l"], 10)
		result.Price, _ = strconv.ParseFloat(value.MsgArray[0]["z"], 10)
		result.LimitUp, _ = strconv.ParseFloat(value.MsgArray[0]["u"], 10)
		result.LimitDown, _ = strconv.ParseFloat(value.MsgArray[0]["w"], 10)
		result.Volume, _ = strconv.ParseFloat(value.MsgArray[0]["tv"], 10)
		result.VolumeAcc, _ = strconv.ParseFloat(value.MsgArray[0]["v"], 10)
		result.YesterdayPrice, _ = strconv.ParseFloat(value.MsgArray[0]["y"], 10)
		tlong, _ := strconv.ParseInt(value.MsgArray[0]["tlong"], 10, 64)
		result.TradeTime = time.Unix(tlong/1000, 0)

		result.Info.No = value.MsgArray[0]["c"]
		result.Info.FullName = value.MsgArray[0]["nf"]
		result.Info.Name = value.MsgArray[0]["n"]
		result.Info.Ticker = value.MsgArray[0]["ch"]
		result.Info.Exchange = value.MsgArray[0]["ex"]
		result.Info.Category = value.MsgArray[0]["i"]

		result.SysInfo = make(map[string]interface{})
		result.SysInfo = value.QueryTime
	}
	return result, err
}

// NewTWSE 建立一個上市股票
func NewTWSE(No string, Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       No,
		Date:     Date,
		Exchange: "tse",
	}
}

// NewOTC 建立一個上櫃股票
func NewOTC(No string, Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       No,
		Date:     Date,
		Exchange: "otc",
	}
}

// NewWeight 大盤指數
func NewWeight(Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       "t00",
		Date:     Date,
		Exchange: "tse",
	}
}

// NewOTCI 上櫃指數
func NewOTCI(Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       "o00",
		Date:     Date,
		Exchange: "otc",
	}
}

// NewFRMSA 寶島指數
func NewFRMSA(Date time.Time) *StockRealTime {
	return &StockRealTime{
		No:       "FRMSA",
		Date:     Date,
		Exchange: "tse",
	}
}
<|endoftext|>


<commit_before>package omxplayer

import (
	log "github.com/Sirupsen/logrus"
	"github.com/guelfey/go.dbus"
	"os"
)

const (
	envDisplay         = "DISPLAY"
	envDbusAddress     = "DBUS_SESSION_BUS_ADDRESS"
	envDbusPid         = "DBUS_SESSION_BUS_PID"
	prefixOmxDbusFiles = "/tmp/omxplayerdbus."
	suffixOmxDbusPid   = ".pid"
	ifaceMpris         = "org.mpris.MediaPlayer2"
)

var (
	user            string
	home            string
	fileOmxDbusPath string
	fileOmxDbusPid  string
)

func init() {
	SetUser(os.Getenv("USER"), os.Getenv("HOME"))
}

func SetUser(u, h string) {
	user = u
	home = h
	fileOmxDbusPath = prefixOmxDbusFiles + user
	fileOmxDbusPid = prefixOmxDbusFiles + user + suffixOmxDbusPid
}

// New returns a new Player instance that can be used to control an OMXPlayer
// instance that is playing the video located at the specified URL.
func New(url string) (player *Player, err error) {
	removeDbusFiles()
	return
}

// removeDbusFiles removes the files that OMXPlayer creates containing the D-Bus
// path and PID. This ensures that when the path and PID are read in, the new
// files are read instead of the old ones.
func removeDbusFiles() {
	removeFile(fileOmxDbusPath)
	removeFile(fileOmxDbusPid)
}

// getDbusPath reads the D-Bus path from the file OMXPlayer writes it's path to.
// If the file cannot be read, it returns an error, otherwise it returns the
// path as a string.
func getDbusPath() (string, error) {
	if err := waitForFile(fileOmxDbusPath); err != nil {
		return "", err
	}
	return readFile(fileOmxDbusPath)
}

// getDbusPath reads the D-Bus PID from the file OMXPlayer writes it's PID to.
// If the file cannot be read, it returns an error, otherwise it returns the
// PID as a string.
func getDbusPid() (string, error) {
	if err := waitForFile(fileOmxDbusPid); err != nil {
		return "", err
	}
	return readFile(fileOmxDbusPid)
}

// getDbusConnection establishes and returns a D-Bus connection to the specified
// D-Bus path with the specified given D-Bus PID. Since the connection's `Auth`
// method attempts to use Go's `os/user` package to get the current user's name
// and home directory, and `os/user` is not implemented for Linux-ARM, the
// `authMethods` parameter is specified explicitly rather than passing `nil`.
func getDbusConnection(path, pid string) (conn *dbus.Conn, err error) {
	authMethods := []dbus.Auth{
		dbus.AuthExternal(user),
		dbus.AuthCookieSha1(user, home),
	}

	log.Debug("omxplayer: opening dbus session")
	if conn, err = dbus.SessionBusPrivate(); err != nil {
		return
	}

	log.Debug("omxplayer: authenticating dbus session")
	if err = conn.Auth(authMethods); err != nil {
		return
	}

	log.Debug("omxplayer: initializing dbus session")
	err = conn.Hello()
	return
}

// setupDbusEnvironment sets the environment variables that are necessary to
// establish a D-Bus connection. If the connection's path or PID cannot be read,
// the associated error is returned.
func setupDbusEnvironment() (err error) {
	log.Debug("omxplayer: setting up dbus environment")

	path, err := getDbusPath()
	if err != nil {
		return
	}

	pid, err := getDbusPid()
	if err != nil {
		return
	}

	setEnv(envDisplay, ":0")
	setEnv(envDbusAddress, path)
	setEnv(envDbusPid, pid)
	return
}
<commit_msg>Implement function to fork/exec an OMXPlayer.<commit_after>package omxplayer

import (
	log "github.com/Sirupsen/logrus"
	"github.com/guelfey/go.dbus"
	"os"
	"os/exec"
	"strings"
)

const (
	envDisplay         = "DISPLAY"
	envDbusAddress     = "DBUS_SESSION_BUS_ADDRESS"
	envDbusPid         = "DBUS_SESSION_BUS_PID"
	prefixOmxDbusFiles = "/tmp/omxplayerdbus."
	suffixOmxDbusPid   = ".pid"
	ifaceMpris         = "org.mpris.MediaPlayer2"
	exeOxmPlayer       = "omxplayer"
	keyPause           = "p"
)

var (
	user            string
	home            string
	fileOmxDbusPath string
	fileOmxDbusPid  string
)

func init() {
	SetUser(os.Getenv("USER"), os.Getenv("HOME"))
}

func SetUser(u, h string) {
	user = u
	home = h
	fileOmxDbusPath = prefixOmxDbusFiles + user
	fileOmxDbusPid = prefixOmxDbusFiles + user + suffixOmxDbusPid
}

// New returns a new Player instance that can be used to control an OMXPlayer
// instance that is playing the video located at the specified URL.
func New(url string) (player *Player, err error) {
	removeDbusFiles()
	return
}

// removeDbusFiles removes the files that OMXPlayer creates containing the D-Bus
// path and PID. This ensures that when the path and PID are read in, the new
// files are read instead of the old ones.
func removeDbusFiles() {
	removeFile(fileOmxDbusPath)
	removeFile(fileOmxDbusPid)
}

// getDbusPath reads the D-Bus path from the file OMXPlayer writes it's path to.
// If the file cannot be read, it returns an error, otherwise it returns the
// path as a string.
func getDbusPath() (string, error) {
	if err := waitForFile(fileOmxDbusPath); err != nil {
		return "", err
	}
	return readFile(fileOmxDbusPath)
}

// getDbusPath reads the D-Bus PID from the file OMXPlayer writes it's PID to.
// If the file cannot be read, it returns an error, otherwise it returns the
// PID as a string.
func getDbusPid() (string, error) {
	if err := waitForFile(fileOmxDbusPid); err != nil {
		return "", err
	}
	return readFile(fileOmxDbusPid)
}

// getDbusConnection establishes and returns a D-Bus connection to the specified
// D-Bus path with the specified given D-Bus PID. Since the connection's `Auth`
// method attempts to use Go's `os/user` package to get the current user's name
// and home directory, and `os/user` is not implemented for Linux-ARM, the
// `authMethods` parameter is specified explicitly rather than passing `nil`.
func getDbusConnection(path, pid string) (conn *dbus.Conn, err error) {
	authMethods := []dbus.Auth{
		dbus.AuthExternal(user),
		dbus.AuthCookieSha1(user, home),
	}

	log.Debug("omxplayer: opening dbus session")
	if conn, err = dbus.SessionBusPrivate(); err != nil {
		return
	}

	log.Debug("omxplayer: authenticating dbus session")
	if err = conn.Auth(authMethods); err != nil {
		return
	}

	log.Debug("omxplayer: initializing dbus session")
	err = conn.Hello()
	return
}

// setupDbusEnvironment sets the environment variables that are necessary to
// establish a D-Bus connection. If the connection's path or PID cannot be read,
// the associated error is returned.
func setupDbusEnvironment() (err error) {
	log.Debug("omxplayer: setting up dbus environment")

	path, err := getDbusPath()
	if err != nil {
		return
	}

	pid, err := getDbusPid()
	if err != nil {
		return
	}

	setEnv(envDisplay, ":0")
	setEnv(envDbusAddress, path)
	setEnv(envDbusPid, pid)
	return
}

// execOmxplayer starts a new OMXPlayer process and tells it to pause the video
// by passing a "p" on standard input.
func execOmxplayer(url string) (cmd *exec.Cmd, err error) {
	log.Debug("omxplayer: starting omxplayer process")

	cmd = exec.Command(exeOxmPlayer, "--no-osd", url)
	cmd.Stdin = strings.NewReader(keyPause)
	err = cmd.Start()
	return
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"net"
)

func tcpListenerFactory(config map[string]string) (net.Listener, error) {
	addr, ok := config["address"]
	if !ok {
		return nil, fmt.Errorf("'address' must be set")
	}

	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return nil, err
	}

	return listenerWrapTLS(ln, config)
}
<commit_msg>command/sever: copy the TCP keep alive listener<commit_after>package server

import (
	"fmt"
	"net"
	"time"
)

func tcpListenerFactory(config map[string]string) (net.Listener, error) {
	addr, ok := config["address"]
	if !ok {
		return nil, fmt.Errorf("'address' must be set")
	}

	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return nil, err
	}

	ln = tcpKeepAliveListener{ln.(*net.TCPListener)}
	return listenerWrapTLS(ln, config)
}

// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// This is copied directly from the Go source code.
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"github.com/franela/goblin"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"os"
	"testing"
)

type FakeResponseBodyGenerator struct {
	use string
}

func (instance *FakeResponseBodyGenerator) Generate() string {
	return instance.use
}

func (instance *FakeResponseBodyGenerator) UseString(value string) {
	instance.use = value
}

func NewFakeResponseBodyGenerator() *FakeResponseBodyGenerator {
	return &FakeResponseBodyGenerator{""}
}

var fakeResponseBodyGenerator *FakeResponseBodyGenerator
var enanosHttpHandlerFactory *DefaultEnanosHttpHandlerFactory

func TestMain(m *testing.M) {
	fakeResponseBodyGenerator = NewFakeResponseBodyGenerator()
	enanosHttpHandlerFactory = NewDefaultEnanosHttpHandlerFactory(fakeResponseBodyGenerator)
	go func() {
		StartEnanos(fakeResponseBodyGenerator, enanosHttpHandlerFactory)
	}()
	os.Exit(m.Run())
}

func Test_ResponseBodyGenerator(t *testing.T) {
	g := goblin.Goblin(t)
	g.Describe("Default Response Body Generator", func() {
		g.It("generates a string of the defined lenth", func() {
			maxLength := 5
			generator := NewDefaultResponseBodyGenerator(maxLength)
			value := generator.Generate()
			assert.Equal(t, maxLength, len(value))
		})
	})

	g.Describe("Random Response Body Generator", func() {
		g.It("generates a string of length between the defined min length and the defined max length", func() {
			minLength := 50
			maxLength := 500
			generator := NewRandomResponseBodyGenerator(minLength, maxLength)
			value := generator.Generate()
			assert.True(t, len(value) >= minLength && len(value) <= maxLength)
		})
	})
}

func SendHelloWorldByHttpMethod(method string, url string) (resp *http.Response, err error) {
	var jsonStr = []byte(`{"message":"hello world"}`)
	req, err := http.NewRequest(method, url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{}
	resp, err = client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return
}

func Test_Enanos(t *testing.T) {
	g := goblin.Goblin(t)
	g.Describe("Enanos Server:", func() {

		url := func(path string) (fullPath string) {
			fullPath = "http://localhost:8000" + path
			return
		}

		g.Describe("Happy :", func() {
			var happyUrl string
			g.Before(func() {
				happyUrl = url("/default/happy")
			})
			g.It("GET returns 200", func() {
				resp, _ := http.Get(happyUrl)
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})

			g.It("POST returns 200", func() {
				resp, _ := SendHelloWorldByHttpMethod("POST", happyUrl)
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})

			g.It("PUT returns 200", func() {
				resp, _ := SendHelloWorldByHttpMethod("PUT", happyUrl)
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})

			g.It("DELETE returns 200", func() {
				resp, _ := SendHelloWorldByHttpMethod("DELETE", happyUrl)
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})
		})

		g.Describe("Grumpy :", func() {
			var grumpyUrl string
			g.Before(func() {
				grumpyUrl = url("/default/grumpy")
			})
			g.It("GET returns 500", func() {
				resp, _ := http.Get(grumpyUrl)
				assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
			})
			g.It("POST returns 500", func() {
				resp, _ := SendHelloWorldByHttpMethod("POST", grumpyUrl)
				assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
			})
		})

		g.Describe("Sneezy :", func() {
			g.It("GET returns 200", func() {
				resp, _ := http.Get(url("/default/sneezy"))
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})

			g.It("GET returns random response body", func() {
				sample := "foobar"
				fakeResponseBodyGenerator.UseString(sample)
				resp, _ := http.Get(url("/default/sneezy"))
				defer resp.Body.Close()
				body, _ := ioutil.ReadAll(resp.Body)
				assert.Equal(t, sample, string(body))
			})
		})
	})
}
<commit_msg>Grumpy PUT returns 500<commit_after>package main

import (
	"bytes"
	"github.com/franela/goblin"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"os"
	"testing"
)

type FakeResponseBodyGenerator struct {
	use string
}

func (instance *FakeResponseBodyGenerator) Generate() string {
	return instance.use
}

func (instance *FakeResponseBodyGenerator) UseString(value string) {
	instance.use = value
}

func NewFakeResponseBodyGenerator() *FakeResponseBodyGenerator {
	return &FakeResponseBodyGenerator{""}
}

var fakeResponseBodyGenerator *FakeResponseBodyGenerator
var enanosHttpHandlerFactory *DefaultEnanosHttpHandlerFactory

func TestMain(m *testing.M) {
	fakeResponseBodyGenerator = NewFakeResponseBodyGenerator()
	enanosHttpHandlerFactory = NewDefaultEnanosHttpHandlerFactory(fakeResponseBodyGenerator)
	go func() {
		StartEnanos(fakeResponseBodyGenerator, enanosHttpHandlerFactory)
	}()
	os.Exit(m.Run())
}

func Test_ResponseBodyGenerator(t *testing.T) {
	g := goblin.Goblin(t)
	g.Describe("Default Response Body Generator", func() {
		g.It("generates a string of the defined lenth", func() {
			maxLength := 5
			generator := NewDefaultResponseBodyGenerator(maxLength)
			value := generator.Generate()
			assert.Equal(t, maxLength, len(value))
		})
	})

	g.Describe("Random Response Body Generator", func() {
		g.It("generates a string of length between the defined min length and the defined max length", func() {
			minLength := 50
			maxLength := 500
			generator := NewRandomResponseBodyGenerator(minLength, maxLength)
			value := generator.Generate()
			assert.True(t, len(value) >= minLength && len(value) <= maxLength)
		})
	})
}

func SendHelloWorldByHttpMethod(method string, url string) (resp *http.Response, err error) {
	var jsonStr = []byte(`{"message":"hello world"}`)
	req, err := http.NewRequest(method, url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{}
	resp, err = client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return
}

func Test_Enanos(t *testing.T) {
	g := goblin.Goblin(t)
	g.Describe("Enanos Server:", func() {

		url := func(path string) (fullPath string) {
			fullPath = "http://localhost:8000" + path
			return
		}

		g.Describe("Happy :", func() {
			var happyUrl string
			g.Before(func() {
				happyUrl = url("/default/happy")
			})
			g.It("GET returns 200", func() {
				resp, _ := http.Get(happyUrl)
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})

			g.It("POST returns 200", func() {
				resp, _ := SendHelloWorldByHttpMethod("POST", happyUrl)
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})

			g.It("PUT returns 200", func() {
				resp, _ := SendHelloWorldByHttpMethod("PUT", happyUrl)
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})

			g.It("DELETE returns 200", func() {
				resp, _ := SendHelloWorldByHttpMethod("DELETE", happyUrl)
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})
		})

		g.Describe("Grumpy :", func() {
			var grumpyUrl string
			g.Before(func() {
				grumpyUrl = url("/default/grumpy")
			})
			g.It("GET returns 500", func() {
				resp, _ := http.Get(grumpyUrl)
				assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
			})

			g.It("POST returns 500", func() {
				resp, _ := SendHelloWorldByHttpMethod("POST", grumpyUrl)
				assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
			})

			g.It("PUT returns 500", func() {
				resp, _ := SendHelloWorldByHttpMethod("PUT", grumpyUrl)
				assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
			})
		})

		g.Describe("Sneezy :", func() {
			g.It("GET returns 200", func() {
				resp, _ := http.Get(url("/default/sneezy"))
				assert.Equal(t, http.StatusOK, resp.StatusCode)
			})

			g.It("GET returns random response body", func() {
				sample := "foobar"
				fakeResponseBodyGenerator.UseString(sample)
				resp, _ := http.Get(url("/default/sneezy"))
				defer resp.Body.Close()
				body, _ := ioutil.ReadAll(resp.Body)
				assert.Equal(t, sample, string(body))
			})
		})
	})
}
<|endoftext|>


<commit_before>// +build autoupdate

package commands

func init() {
	EnableAutoUpdate = true
}
<commit_msg>Remove obsolete auto-updater file<commit_after><|endoftext|>


<commit_before>package gass

func calcIndentLevel(s string) int {
	i := 0
	for _, c := range s {
		if c == 32 {
			i += 1
		} else if c == 9 {
			i += 1
			for i%8 != 0 {
				i += 1
			}
		} else if c != 13 {
			break
		}
	}
	return i
}
<commit_msg>minor optimization<commit_after>package gass

func calcIndentLevel(s string) int {
	i := 0
	for _, c := range s {
		if c == 32 {
			i += 1
		} else if c == 9 {
			i = ((i + 8) / 8) * 8
		} else if c != 13 {
			break
		}
	}
	return i
}
<|endoftext|>


<commit_before>package cfitsio

import (
	"fmt"
	"strings"
)

// strIsContinued checks whether the last non-whitespace char in the string
// is an ampersand, which indicates the value string is to be continued on the
// next line
func strIsContinued(v string) bool {
	vv := strings.Trim(v, " \n\t'")
	fmt.Printf("vv=%q\n", vv)
	if len(vv) == 0 {
		return false
	}
	return vv[len(vv)-1] == '&'
}

// EOF
<commit_msg>utils: remove debug printout<commit_after>package cfitsio

import (
	"strings"
)

// strIsContinued checks whether the last non-whitespace char in the string
// is an ampersand, which indicates the value string is to be continued on the
// next line
func strIsContinued(v string) bool {
	vv := strings.Trim(v, " \n\t'")
	if len(vv) == 0 {
		return false
	}
	return vv[len(vv)-1] == '&'
}

// EOF
<|endoftext|>


<commit_before>// Package libsacloud is the SAKURA CLOUD API v1.1 Client for Go.
package libsacloud

// Version バージョン
const Version = "1.24.0"
<commit_msg>Bump to v1.24.1<commit_after>// Package libsacloud is the SAKURA CLOUD API v1.1 Client for Go.
package libsacloud

// Version バージョン
const Version = "1.24.1"
<|endoftext|>


<commit_before>package mpb

import (
	"bytes"
	"testing"
	"unicode/utf8"
)

func TestDraw(t *testing.T) {
	// key is termWidth
	testSuite := map[int][]struct {
		name           string
		style          string
		total, current int64
		barWidth       int
		trim           bool
		reverse        bool
		refill         int64
		want           string
	}{
		0: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "…  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "",
			},
		},
		1: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "…  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "",
			},
		},
		2: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[]",
			},
		},
		3: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[-]",
			},
		},
		4: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>-]",
			},
		},
		5: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [-] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>--]",
			},
		},
		6: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>-] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>---]",
			},
		},
		7: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>--] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=>---]",
			},
		},
		8: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>---] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=>----]",
			},
		},
		80: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [========================>---------------------------------------------------] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=========================>----------------------------------------------------]",
			},
			{
				name:     "t,c,bw{60,20,60}",
				total:    60,
				current:  20,
				barWidth: 60,
				want:     " [==================>---------------------------------------] ",
			},
			{
				name:     "t,c,bw{60,20,60}trim",
				total:    60,
				current:  20,
				barWidth: 60,
				trim:     true,
				want:     "[==================>---------------------------------------]",
			},
		},
		100: {
			{
				name:    "t,c{100,0}",
				total:   100,
				current: 0,
				want:    " [------------------------------------------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,0}trim",
				total:   100,
				current: 0,
				trim:    true,
				want:    "[--------------------------------------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,1}",
				total:   100,
				current: 1,
				want:    " [>-----------------------------------------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,1}trim",
				total:   100,
				current: 1,
				trim:    true,
				want:    "[>-------------------------------------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,99}",
				total:   100,
				current: 99,
				want:    " [==============================================================================================>-] ",
			},
			{
				name:    "t,c{100,99}trim",
				total:   100,
				current: 99,
				trim:    true,
				want:    "[================================================================================================>-]",
			},
			{
				name:    "t,c{100,100}",
				total:   100,
				current: 100,
				want:    " [================================================================================================] ",
			},
			{
				name:    "t,c{100,100}trim",
				total:   100,
				current: 100,
				trim:    true,
				want:    "[==================================================================================================]",
			},
			{
				name:    "t,c,r{100,100,100}trim",
				total:   100,
				current: 100,
				refill:  100,
				trim:    true,
				want:    "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]",
			},
			{
				name:    "t,c{100,33}",
				total:   100,
				current: 33,
				want:    " [===============================>----------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,33}trim",
				total:   100,
				current: 33,
				trim:    true,
				want:    "[===============================>------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,33}trim,rev",
				total:   100,
				current: 33,
				trim:    true,
				reverse: true,
				want:    "[------------------------------------------------------------------<===============================]",
			},
			{
				name:    "t,c,r{100,33,33}",
				total:   100,
				current: 33,
				refill:  33,
				want:    " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ",
			},
			{
				name:    "t,c,r{100,33,33}trim",
				total:   100,
				current: 33,
				refill:  33,
				trim:    true,
				want:    "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]",
			},
			{
				name:    "t,c,r{100,33,33}trim,rev",
				total:   100,
				current: 33,
				refill:  33,
				trim:    true,
				reverse: true,
				want:    "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]",
			},
			{
				name:    "t,c,r{100,40,33}",
				total:   100,
				current: 40,
				refill:  33,
				want:    " [+++++++++++++++++++++++++++++++======>----------------------------------------------------------] ",
			},
			{
				name:    "t,c,r{100,40,33}trim",
				total:   100,
				current: 40,
				refill:  33,
				trim:    true,
				want:    "[+++++++++++++++++++++++++++++++=======>-----------------------------------------------------------]",
			},
			{
				name:    "t,c,r{100,40,33},rev",
				total:   100,
				current: 40,
				refill:  33,
				reverse: true,
				want:    " [----------------------------------------------------------<======+++++++++++++++++++++++++++++++] ",
			},
			{
				name:    "t,c,r{100,40,33}trim,rev",
				total:   100,
				current: 40,
				refill:  33,
				trim:    true,
				reverse: true,
				want:    "[-----------------------------------------------------------<=======+++++++++++++++++++++++++++++++]",
			},
			{
				name:    "[=の-] t,c{100,1}",
				style:   "[=の-]",
				total:   100,
				current: 1,
				want:    " [の---------------------------------------------------------------------------------------------…] ",
			},
		},
		197: {
			{
				name:     "t,c,r{97486999,2805950,2805483}trim",
				total:    97486999,
				current:  2805950,
				refill:   2805483,
				barWidth: 60,
				trim:     true,
				want:     "[+>--------------------------------------------------------]",
			},
		},
	}

	var tmpBuf bytes.Buffer
	for tw, cases := range testSuite {
		for _, tc := range cases {
			s := newTestState(tc.style, tc.reverse)
			s.reqWidth = tc.barWidth
			s.total = tc.total
			s.current = tc.current
			s.trimSpace = tc.trim
			if tc.refill > 0 {
				if f, ok := s.filler.(interface{ SetRefill(int64) }); ok {
					f.SetRefill(tc.refill)
				}
			}
			tmpBuf.Reset()
			tmpBuf.ReadFrom(s.draw(newStatistics(tw, s)))
			by := tmpBuf.Bytes()

			got := string(by[:len(by)-1])
			if !utf8.ValidString(got) {
				t.Fail()
			}
			if got != tc.want {
				t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got))
			}
		}
	}
}

func newTestState(style string, reverse bool) *bState {
	if style == "" {
		style = DefaultBarStyle
	}
	s := &bState{
		filler: NewBarFiller(style, reverse),
		bufP:   new(bytes.Buffer),
		bufB:   new(bytes.Buffer),
		bufA:   new(bytes.Buffer),
	}
	return s
}
<commit_msg>reorder fields<commit_after>package mpb

import (
	"bytes"
	"testing"
	"unicode/utf8"
)

func TestDraw(t *testing.T) {
	// key is termWidth
	testSuite := map[int][]struct {
		name     string
		style    string
		total    int64
		current  int64
		refill   int64
		barWidth int
		trim     bool
		reverse  bool
		want     string
	}{
		0: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "…  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "",
			},
		},
		1: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "…  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "",
			},
		},
		2: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[]",
			},
		},
		3: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[-]",
			},
		},
		4: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>-]",
			},
		},
		5: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [-] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>--]",
			},
		},
		6: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>-] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>---]",
			},
		},
		7: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>--] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=>---]",
			},
		},
		8: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>---] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=>----]",
			},
		},
		80: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [========================>---------------------------------------------------] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=========================>----------------------------------------------------]",
			},
			{
				name:     "t,c,bw{60,20,60}",
				total:    60,
				current:  20,
				barWidth: 60,
				want:     " [==================>---------------------------------------] ",
			},
			{
				name:     "t,c,bw{60,20,60}trim",
				total:    60,
				current:  20,
				barWidth: 60,
				trim:     true,
				want:     "[==================>---------------------------------------]",
			},
		},
		100: {
			{
				name:    "t,c{100,0}",
				total:   100,
				current: 0,
				want:    " [------------------------------------------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,0}trim",
				total:   100,
				current: 0,
				trim:    true,
				want:    "[--------------------------------------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,1}",
				total:   100,
				current: 1,
				want:    " [>-----------------------------------------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,1}trim",
				total:   100,
				current: 1,
				trim:    true,
				want:    "[>-------------------------------------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,99}",
				total:   100,
				current: 99,
				want:    " [==============================================================================================>-] ",
			},
			{
				name:    "t,c{100,99}trim",
				total:   100,
				current: 99,
				trim:    true,
				want:    "[================================================================================================>-]",
			},
			{
				name:    "t,c{100,100}",
				total:   100,
				current: 100,
				want:    " [================================================================================================] ",
			},
			{
				name:    "t,c{100,100}trim",
				total:   100,
				current: 100,
				trim:    true,
				want:    "[==================================================================================================]",
			},
			{
				name:    "t,c,r{100,100,100}trim",
				total:   100,
				current: 100,
				refill:  100,
				trim:    true,
				want:    "[++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++]",
			},
			{
				name:    "t,c{100,33}",
				total:   100,
				current: 33,
				want:    " [===============================>----------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,33}trim",
				total:   100,
				current: 33,
				trim:    true,
				want:    "[===============================>------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,33}trim,rev",
				total:   100,
				current: 33,
				trim:    true,
				reverse: true,
				want:    "[------------------------------------------------------------------<===============================]",
			},
			{
				name:    "t,c,r{100,33,33}",
				total:   100,
				current: 33,
				refill:  33,
				want:    " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ",
			},
			{
				name:    "t,c,r{100,33,33}trim",
				total:   100,
				current: 33,
				refill:  33,
				trim:    true,
				want:    "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]",
			},
			{
				name:    "t,c,r{100,33,33}trim,rev",
				total:   100,
				current: 33,
				refill:  33,
				trim:    true,
				reverse: true,
				want:    "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]",
			},
			{
				name:    "t,c,r{100,40,33}",
				total:   100,
				current: 40,
				refill:  33,
				want:    " [+++++++++++++++++++++++++++++++======>----------------------------------------------------------] ",
			},
			{
				name:    "t,c,r{100,40,33}trim",
				total:   100,
				current: 40,
				refill:  33,
				trim:    true,
				want:    "[+++++++++++++++++++++++++++++++=======>-----------------------------------------------------------]",
			},
			{
				name:    "t,c,r{100,40,33},rev",
				total:   100,
				current: 40,
				refill:  33,
				reverse: true,
				want:    " [----------------------------------------------------------<======+++++++++++++++++++++++++++++++] ",
			},
			{
				name:    "t,c,r{100,40,33}trim,rev",
				total:   100,
				current: 40,
				refill:  33,
				trim:    true,
				reverse: true,
				want:    "[-----------------------------------------------------------<=======+++++++++++++++++++++++++++++++]",
			},
			{
				name:    "[=の-] t,c{100,1}",
				style:   "[=の-]",
				total:   100,
				current: 1,
				want:    " [の---------------------------------------------------------------------------------------------…] ",
			},
		},
		197: {
			{
				name:     "t,c,r{97486999,2805950,2805483}trim",
				total:    97486999,
				current:  2805950,
				refill:   2805483,
				barWidth: 60,
				trim:     true,
				want:     "[+>--------------------------------------------------------]",
			},
		},
	}

	var tmpBuf bytes.Buffer
	for tw, cases := range testSuite {
		for _, tc := range cases {
			s := newTestState(tc.style, tc.reverse)
			s.reqWidth = tc.barWidth
			s.total = tc.total
			s.current = tc.current
			s.trimSpace = tc.trim
			if tc.refill > 0 {
				if f, ok := s.filler.(interface{ SetRefill(int64) }); ok {
					f.SetRefill(tc.refill)
				}
			}
			tmpBuf.Reset()
			tmpBuf.ReadFrom(s.draw(newStatistics(tw, s)))
			by := tmpBuf.Bytes()

			got := string(by[:len(by)-1])
			if !utf8.ValidString(got) {
				t.Fail()
			}
			if got != tc.want {
				t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got))
			}
		}
	}
}

func newTestState(style string, reverse bool) *bState {
	if style == "" {
		style = DefaultBarStyle
	}
	s := &bState{
		filler: NewBarFiller(style, reverse),
		bufP:   new(bytes.Buffer),
		bufB:   new(bytes.Buffer),
		bufA:   new(bytes.Buffer),
	}
	return s
}
<|endoftext|>


<commit_before>package core

// Signature represents function signature.
type Signature struct {
	positionals halfSignature
	keywords    halfSignature
}

// NewSignature defines a new Signature.
func NewSignature(
	pr []string, po []OptionalArgument, pp string,
	kr []string, ko []OptionalArgument, kk string) Signature {
	return Signature{halfSignature{pr, po, pp}, halfSignature{kr, ko, kk}}
}

// Bind binds Arguments to names defined in Signature and returns full
// arguments to be passed to a function.
func (s Signature) Bind(args Arguments) ([]Value, Value) {
	ps, err := s.positionals.bindPositionals(&args)

	if err != nil {
		return nil, err
	}

	ks, err := s.keywords.bindKeywords(&args)

	if err != nil {
		return nil, err
	}

	if err := args.empty(); err != nil {
		return nil, err
	}

	return append(ps, ks...), nil
}

func (s Signature) arity() int {
	return s.positionals.arity() + s.keywords.arity()
}
<commit_msg>Refactor signature.go<commit_after>package core

// Signature represents function signature.
type Signature struct {
	positionals, keywords halfSignature
}

// NewSignature defines a new Signature.
func NewSignature(
	pr []string, po []OptionalArgument, pp string,
	kr []string, ko []OptionalArgument, kk string) Signature {
	return Signature{halfSignature{pr, po, pp}, halfSignature{kr, ko, kk}}
}

// Bind binds Arguments to names defined in Signature and returns full
// arguments to be passed to a function.
func (s Signature) Bind(args Arguments) ([]Value, Value) {
	ps, err := s.positionals.bindPositionals(&args)

	if err != nil {
		return nil, err
	}

	ks, err := s.keywords.bindKeywords(&args)

	if err != nil {
		return nil, err
	}

	if err := args.empty(); err != nil {
		return nil, err
	}

	return append(ps, ks...), nil
}

func (s Signature) arity() int {
	return s.positionals.arity() + s.keywords.arity()
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"io"
	"net"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/docker/docker/pkg/term"
	"github.com/gravitational/teleport"
	"github.com/gravitational/teleport/lib/defaults"
	"github.com/gravitational/teleport/lib/session"
	"github.com/gravitational/teleport/lib/sshutils"
	"github.com/gravitational/teleport/lib/utils"
	"github.com/gravitational/trace"
	"golang.org/x/crypto/ssh"

	log "github.com/Sirupsen/logrus"
)

type NodeSession struct {
	// id is the Teleport session ID
	id session.ID

	// env is the environment variables that need to be created
	// on the server for this session
	env map[string]string

	// nodeClient is the parent of this session: the client connected to an
	// SSH node
	nodeClient *NodeClient

	// Standard input/outputs for this session
	stdin  io.Reader
	stdout io.Writer
	stderr io.Writer

	// closer is used to simultaneously close all goroutines created by
	// this session. It's also used to wait for everyone to close
	closer *utils.CloseBroadcaster

	ExitMsg string
}

// newSession creates a new Teleport session with the given remote node
// if 'joinSessin' is given, the session will join the existing session
// of another user
func newSession(client *NodeClient,
	joinSession *session.Session,
	env map[string]string,
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer) (*NodeSession, error) {

	if stdin == nil {
		stdin = os.Stdin
	}
	if stdout == nil {
		stdout = os.Stdout
	}
	if stderr == nil {
		stderr = os.Stderr
	}

	var err error
	ns := &NodeSession{
		env:        env,
		nodeClient: client,
		stdin:      stdin,
		stdout:     stdout,
		stderr:     stderr,
		closer:     utils.NewCloseBroadcaster(),
	}
	// if we're joining an existing session, we need to assume that session's
	// existing/current terminal size:
	if joinSession != nil {
		ns.id = joinSession.ID
		tsize := joinSession.TerminalParams.Winsize()
		if ns.isTerminalAttached() {
			err = term.SetWinsize(0, tsize)
			if err != nil {
				log.Error(err)
			}
			os.Stdout.Write([]byte(fmt.Sprintf("\x1b[8;%d;%dt", tsize.Height, tsize.Width)))
		}
		// new session!
	} else {
		ns.id = session.NewID()
	}
	if ns.env == nil {
		ns.env = make(map[string]string)
	}
	ns.env[sshutils.SessionEnvVar] = string(ns.id)
	return ns, nil
}

func (ns *NodeSession) regularSession(callback func(s *ssh.Session) error) error {
	session, err := ns.createServerSession()
	if err != nil {
		return trace.Wrap(err)
	}
	session.Stdout = ns.stdout
	session.Stderr = ns.stderr
	session.Stdin = ns.stdin
	return trace.Wrap(callback(session))
}

type interactiveCallback func(serverSession *ssh.Session, shell io.ReadWriteCloser) error

func (ns *NodeSession) createServerSession() (*ssh.Session, error) {
	sess, err := ns.nodeClient.Client.NewSession()
	if err != nil {
		return nil, trace.Wrap(err)
	}
	// pass language info into the remote session.
	evarsToPass := []string{"LANG", "LANGUAGE"}
	for _, evar := range evarsToPass {
		if value := os.Getenv(evar); value != "" {
			err = sess.Setenv(evar, value)
			if err != nil {
				log.Warn(err)
			}
		}
	}
	// pass environment variables set by client
	for key, val := range ns.env {
		err = sess.Setenv(key, val)
		if err != nil {
			log.Warn(err)
		}
	}
	return sess, nil
}

// interactiveSession creates an interactive session on the remote node, executes
// the given callback on it, and waits for the session to end
func (ns *NodeSession) interactiveSession(callback interactiveCallback) error {
	// determine what kind of a terminal we need
	termType := os.Getenv("TERM")
	if termType == "" {
		termType = teleport.SafeTerminalType
	}
	ns.env["TERM"] = termType
	// create the server-side session:
	sess, err := ns.createServerSession()
	if err != nil {
		return trace.Wrap(err)
	}
	// allocate terminal on the server:
	remoteTerm, err := ns.allocateTerminal(termType, sess)
	if err != nil {
		return trace.Wrap(err)
	}
	defer remoteTerm.Close()

	// call the passed callback and give them the established
	// ssh session:
	if err := callback(sess, remoteTerm); err != nil {
		return trace.Wrap(err)
	}

	// Catch term signals, but only if we're attached to a real terminal
	if ns.isTerminalAttached() {
		ns.watchSignals(remoteTerm)
	}

	// start piping input into the remote shell and pipe the output from
	// the remote shell into stdout:
	ns.pipeInOut(remoteTerm)

	// switch the terminal to raw mode (and switch back on exit!)
	if ns.isTerminalAttached() {
		ts, err := term.SetRawTerminal(0)
		if err != nil {
			log.Warn(err)
		} else {
			defer term.RestoreTerminal(0, ts)
		}
	}
	// wait for the session to end
	<-ns.closer.C
	return nil
}

// allocateTerminal creates (allocates) a server-side terminal for this session.
func (ns *NodeSession) allocateTerminal(termType string, s *ssh.Session) (io.ReadWriteCloser, error) {
	var err error
	// read the size of the terminal window:
	tsize := &term.Winsize{
		Width:  teleport.DefaultTerminalWidth,
		Height: teleport.DefaultTerminalHeight,
	}
	if ns.isTerminalAttached() {
		tsize, err = term.GetWinsize(0)
		if err != nil {
			log.Error(err)
		}
	}
	// ... and request a server-side terminal of the same size:
	err = s.RequestPty(termType,
		int(tsize.Height),
		int(tsize.Width),
		ssh.TerminalModes{})
	if err != nil {
		return nil, trace.Wrap(err)
	}
	writer, err := s.StdinPipe()
	if err != nil {
		return nil, trace.Wrap(err)
	}
	reader, err := s.StdoutPipe()
	if err != nil {
		return nil, trace.Wrap(err)
	}
	stderr, err := s.StderrPipe()
	if err != nil {
		return nil, trace.Wrap(err)
	}
	if ns.isTerminalAttached() {
		go ns.updateTerminalSize(s)
	}
	go func() {
		io.Copy(os.Stderr, stderr)
	}()
	return utils.NewPipeNetConn(
		reader,
		writer,
		utils.MultiCloser(writer, s, ns.closer),
		&net.IPAddr{},
		&net.IPAddr{},
	), nil
}

func (ns *NodeSession) updateTerminalSize(s *ssh.Session) {
	// sibscribe for "terminal resized" signal:
	sigC := make(chan os.Signal, 1)
	signal.Notify(sigC, syscall.SIGWINCH)
	currentSize, _ := term.GetWinsize(0)

	// start the timer which asks for server-side window size changes:
	siteClient, err := ns.nodeClient.Proxy.ConnectToSite()
	if err != nil {
		log.Error(err)
	}
	tick := time.NewTicker(defaults.SessionRefreshPeriod)
	defer tick.Stop()

	var prevSess *session.Session
	for {
		select {
		// our own terminal window got resized:
		case sig := <-sigC:
			if sig == nil {
				return
			}
			// get the size:
			winSize, err := term.GetWinsize(0)
			if err != nil {
				log.Warnf("[CLIENT] Error getting size: %s", err)
				break
			}
			// it's the result of our own size change (see below)
			if winSize.Height == currentSize.Height && winSize.Width == currentSize.Width {
				continue
			}
			// send the new window size to the server
			_, err = s.SendRequest(
				sshutils.WindowChangeReq, false,
				ssh.Marshal(sshutils.WinChangeReqParams{
					W: uint32(winSize.Width),
					H: uint32(winSize.Height),
				}))
			if err != nil {
				log.Warnf("[CLIENT] failed to send window change reqest: %v", err)
			}
		case <-tick.C:
			sess, err := siteClient.GetSession(ns.id)
			if err != nil {
				log.Error(err)
				continue
			}
			// no previous session
			if prevSess == nil || sess == nil {
				prevSess = sess
				continue
			}
			log.Infof("[CLIENT] updating the session %v with %d parties", sess.ID, len(sess.Parties))
			// nothing changed
			if prevSess.TerminalParams.W == sess.TerminalParams.W && prevSess.TerminalParams.H == sess.TerminalParams.H {
				continue
			}

			newSize := sess.TerminalParams.Winsize()
			currentSize, err = term.GetWinsize(0)
			if err != nil {
				log.Error(err)
			}
			if currentSize.Width != newSize.Width || currentSize.Height != newSize.Height {
				// ok, something have changed, let's resize to the new parameters
				err = term.SetWinsize(0, newSize)
				if err != nil {
					log.Error(err)
				}
				os.Stdout.Write([]byte(fmt.Sprintf("\x1b[8;%d;%dt", newSize.Height, newSize.Width)))
			}
			prevSess = sess
		case <-ns.closer.C:
			return
		}
	}
}

// isTerminalAttached returns true when this session is be controlled by
// a real terminal.
// It will return False for sessions initiated by the Web client or
// for non-interactive sessions (commands)
func (ns *NodeSession) isTerminalAttached() bool {
	return ns.stdin == os.Stdin && term.IsTerminal(os.Stdin.Fd())
}

// runShell executes user's shell on the remote node under an interactive session
func (ns *NodeSession) runShell(callback ShellCreatedCallback) error {
	return ns.interactiveSession(func(s *ssh.Session, shell io.ReadWriteCloser) error {
		// start the shell on the server:
		if err := s.Shell(); err != nil {
			return trace.Wrap(err)
		}
		// call the client-supplied callback
		if callback != nil {
			exit, err := callback(shell)
			if exit {
				return trace.Wrap(err)
			}
		}
		return nil
	})
}

// runCommand executes a given command either in interactive (with terminal attached)
// or non-intractive mode
func (ns *NodeSession) runCommand(cmd []string, callback ShellCreatedCallback, interactive bool) error {
	// stdin is not a terminal? refuse to allocate terminal on the server and go back
	// to "non-interactive":
	if interactive && ns.stdin == os.Stdin && !term.IsTerminal(os.Stdin.Fd()) {
		interactive = false
		fmt.Fprintf(os.Stderr, "TTY will not be allocated on the server because stdin is not a terminal\n")
	}

	// interactive session:
	if interactive {
		return ns.interactiveSession(func(s *ssh.Session, shell io.ReadWriteCloser) error {
			err := s.Start(strings.Join(cmd, " "))
			if err != nil {
				return trace.Wrap(err)
			}
			if callback != nil {
				exit, err := callback(shell)
				if exit {
					return trace.Wrap(err)
				}
			}
			return nil
		})
	}
	// non-interactive session:
	return ns.regularSession(func(s *ssh.Session) error {
		return s.Run(strings.Join(cmd, " "))
	})
}

// watchSignals register UNIX signal handlers and properly terminates a remote shell session
// must be called as a goroutine right after a remote shell is created
func (ns *NodeSession) watchSignals(shell io.Writer) {
	exitSignals := make(chan os.Signal, 1)
	// catch SIGTERM
	signal.Notify(exitSignals, syscall.SIGTERM)
	go func() {
		defer ns.closer.Close()
		<-exitSignals
	}()
	// Catch Ctrl-C signal
	ctrlCSignal := make(chan os.Signal, 1)
	signal.Notify(ctrlCSignal, syscall.SIGINT)
	go func() {
		for {
			<-ctrlCSignal
			_, err := shell.Write([]byte{3})
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}()
	// Catch Ctrl-Z signal
	ctrlZSignal := make(chan os.Signal, 1)
	signal.Notify(ctrlZSignal, syscall.SIGTSTP)
	go func() {
		for {
			<-ctrlZSignal
			_, err := shell.Write([]byte{26})
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}()
}

// pipeInOut launches two goroutines: one to pipe the local input into the remote shell,
// and another to pipe the output of the remote shell into the local output
func (ns *NodeSession) pipeInOut(shell io.ReadWriteCloser) {
	// copy from the remote shell to the local output
	go func() {
		defer ns.closer.Close()
		_, err := io.Copy(ns.stdout, shell)
		if err != nil {
			log.Errorf(err.Error())
		}
	}()
	// copy from the local input to the remote shell:
	go func() {
		defer ns.closer.Close()
		buf := make([]byte, 128)
		for {
			n, err := ns.stdin.Read(buf)
			if err != nil {
				fmt.Println(trace.Wrap(err))
				return
			}
			if n > 0 {
				_, err = shell.Write(buf[:n])
				if err != nil {
					ns.ExitMsg = err.Error()
					return
				}
			}
		}
	}()
}
<commit_msg>Bugfix<commit_after>package client

import (
	"fmt"
	"io"
	"net"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/docker/docker/pkg/term"
	"github.com/gravitational/teleport"
	"github.com/gravitational/teleport/lib/defaults"
	"github.com/gravitational/teleport/lib/session"
	"github.com/gravitational/teleport/lib/sshutils"
	"github.com/gravitational/teleport/lib/utils"
	"github.com/gravitational/trace"
	"golang.org/x/crypto/ssh"

	log "github.com/Sirupsen/logrus"
)

type NodeSession struct {
	// id is the Teleport session ID
	id session.ID

	// env is the environment variables that need to be created
	// on the server for this session
	env map[string]string

	// nodeClient is the parent of this session: the client connected to an
	// SSH node
	nodeClient *NodeClient

	// Standard input/outputs for this session
	stdin  io.Reader
	stdout io.Writer
	stderr io.Writer

	// closer is used to simultaneously close all goroutines created by
	// this session. It's also used to wait for everyone to close
	closer *utils.CloseBroadcaster

	ExitMsg string
}

// newSession creates a new Teleport session with the given remote node
// if 'joinSessin' is given, the session will join the existing session
// of another user
func newSession(client *NodeClient,
	joinSession *session.Session,
	env map[string]string,
	stdin io.Reader,
	stdout io.Writer,
	stderr io.Writer) (*NodeSession, error) {

	if stdin == nil {
		stdin = os.Stdin
	}
	if stdout == nil {
		stdout = os.Stdout
	}
	if stderr == nil {
		stderr = os.Stderr
	}

	var err error
	ns := &NodeSession{
		env:        env,
		nodeClient: client,
		stdin:      stdin,
		stdout:     stdout,
		stderr:     stderr,
		closer:     utils.NewCloseBroadcaster(),
	}
	// if we're joining an existing session, we need to assume that session's
	// existing/current terminal size:
	if joinSession != nil {
		ns.id = joinSession.ID
		tsize := joinSession.TerminalParams.Winsize()
		if ns.isTerminalAttached() {
			err = term.SetWinsize(0, tsize)
			if err != nil {
				log.Error(err)
			}
			os.Stdout.Write([]byte(fmt.Sprintf("\x1b[8;%d;%dt", tsize.Height, tsize.Width)))
		}
		// new session!
	} else {
		ns.id = session.NewID()
	}
	if ns.env == nil {
		ns.env = make(map[string]string)
	}
	ns.env[sshutils.SessionEnvVar] = string(ns.id)
	return ns, nil
}

func (ns *NodeSession) regularSession(callback func(s *ssh.Session) error) error {
	session, err := ns.createServerSession()
	if err != nil {
		return trace.Wrap(err)
	}
	session.Stdout = ns.stdout
	session.Stderr = ns.stderr
	session.Stdin = ns.stdin
	return trace.Wrap(callback(session))
}

type interactiveCallback func(serverSession *ssh.Session, shell io.ReadWriteCloser) error

func (ns *NodeSession) createServerSession() (*ssh.Session, error) {
	sess, err := ns.nodeClient.Client.NewSession()
	if err != nil {
		return nil, trace.Wrap(err)
	}
	// pass language info into the remote session.
	evarsToPass := []string{"LANG", "LANGUAGE"}
	for _, evar := range evarsToPass {
		if value := os.Getenv(evar); value != "" {
			err = sess.Setenv(evar, value)
			if err != nil {
				log.Warn(err)
			}
		}
	}
	// pass environment variables set by client
	for key, val := range ns.env {
		err = sess.Setenv(key, val)
		if err != nil {
			log.Warn(err)
		}
	}
	return sess, nil
}

// interactiveSession creates an interactive session on the remote node, executes
// the given callback on it, and waits for the session to end
func (ns *NodeSession) interactiveSession(callback interactiveCallback) error {
	// determine what kind of a terminal we need
	termType := os.Getenv("TERM")
	if termType == "" {
		termType = teleport.SafeTerminalType
	}
	ns.env["TERM"] = termType
	// create the server-side session:
	sess, err := ns.createServerSession()
	if err != nil {
		return trace.Wrap(err)
	}
	// allocate terminal on the server:
	remoteTerm, err := ns.allocateTerminal(termType, sess)
	if err != nil {
		return trace.Wrap(err)
	}
	defer remoteTerm.Close()

	// call the passed callback and give them the established
	// ssh session:
	if err := callback(sess, remoteTerm); err != nil {
		return trace.Wrap(err)
	}

	// Catch term signals, but only if we're attached to a real terminal
	if ns.isTerminalAttached() {
		ns.watchSignals(remoteTerm)
	}

	// start piping input into the remote shell and pipe the output from
	// the remote shell into stdout:
	ns.pipeInOut(remoteTerm)

	// switch the terminal to raw mode (and switch back on exit!)
	if ns.isTerminalAttached() {
		ts, err := term.SetRawTerminal(0)
		if err != nil {
			log.Warn(err)
		} else {
			defer term.RestoreTerminal(0, ts)
		}
	}
	// wait for the session to end
	<-ns.closer.C
	return nil
}

// allocateTerminal creates (allocates) a server-side terminal for this session.
func (ns *NodeSession) allocateTerminal(termType string, s *ssh.Session) (io.ReadWriteCloser, error) {
	var err error
	// read the size of the terminal window:
	tsize := &term.Winsize{
		Width:  teleport.DefaultTerminalWidth,
		Height: teleport.DefaultTerminalHeight,
	}
	if ns.isTerminalAttached() {
		tsize, err = term.GetWinsize(0)
		if err != nil {
			log.Error(err)
		}
	}
	// ... and request a server-side terminal of the same size:
	err = s.RequestPty(termType,
		int(tsize.Height),
		int(tsize.Width),
		ssh.TerminalModes{})
	if err != nil {
		return nil, trace.Wrap(err)
	}
	writer, err := s.StdinPipe()
	if err != nil {
		return nil, trace.Wrap(err)
	}
	reader, err := s.StdoutPipe()
	if err != nil {
		return nil, trace.Wrap(err)
	}
	stderr, err := s.StderrPipe()
	if err != nil {
		return nil, trace.Wrap(err)
	}
	if ns.isTerminalAttached() {
		go ns.updateTerminalSize(s)
	}
	go func() {
		io.Copy(os.Stderr, stderr)
	}()
	return utils.NewPipeNetConn(
		reader,
		writer,
		utils.MultiCloser(writer, s, ns.closer),
		&net.IPAddr{},
		&net.IPAddr{},
	), nil
}

func (ns *NodeSession) updateTerminalSize(s *ssh.Session) {
	// sibscribe for "terminal resized" signal:
	sigC := make(chan os.Signal, 1)
	signal.Notify(sigC, syscall.SIGWINCH)
	currentSize, _ := term.GetWinsize(0)

	// start the timer which asks for server-side window size changes:
	siteClient, err := ns.nodeClient.Proxy.ConnectToSite()
	if err != nil {
		log.Error(err)
		return
	}
	tick := time.NewTicker(defaults.SessionRefreshPeriod)
	defer tick.Stop()

	var prevSess *session.Session
	for {
		select {
		// our own terminal window got resized:
		case sig := <-sigC:
			if sig == nil {
				return
			}
			// get the size:
			winSize, err := term.GetWinsize(0)
			if err != nil {
				log.Warnf("[CLIENT] Error getting size: %s", err)
				break
			}
			// it's the result of our own size change (see below)
			if winSize.Height == currentSize.Height && winSize.Width == currentSize.Width {
				continue
			}
			// send the new window size to the server
			_, err = s.SendRequest(
				sshutils.WindowChangeReq, false,
				ssh.Marshal(sshutils.WinChangeReqParams{
					W: uint32(winSize.Width),
					H: uint32(winSize.Height),
				}))
			if err != nil {
				log.Warnf("[CLIENT] failed to send window change reqest: %v", err)
			}
		case <-tick.C:
			sess, err := siteClient.GetSession(ns.id)
			if err != nil {
				log.Error(err)
				continue
			}
			// no previous session
			if prevSess == nil || sess == nil {
				prevSess = sess
				continue
			}
			log.Infof("[CLIENT] updating the session %v with %d parties", sess.ID, len(sess.Parties))
			// nothing changed
			if prevSess.TerminalParams.W == sess.TerminalParams.W && prevSess.TerminalParams.H == sess.TerminalParams.H {
				continue
			}

			newSize := sess.TerminalParams.Winsize()
			currentSize, err = term.GetWinsize(0)
			if err != nil {
				log.Error(err)
			}
			if currentSize.Width != newSize.Width || currentSize.Height != newSize.Height {
				// ok, something have changed, let's resize to the new parameters
				err = term.SetWinsize(0, newSize)
				if err != nil {
					log.Error(err)
				}
				os.Stdout.Write([]byte(fmt.Sprintf("\x1b[8;%d;%dt", newSize.Height, newSize.Width)))
			}
			prevSess = sess
		case <-ns.closer.C:
			return
		}
	}
}

// isTerminalAttached returns true when this session is be controlled by
// a real terminal.
// It will return False for sessions initiated by the Web client or
// for non-interactive sessions (commands)
func (ns *NodeSession) isTerminalAttached() bool {
	return ns.stdin == os.Stdin && term.IsTerminal(os.Stdin.Fd())
}

// runShell executes user's shell on the remote node under an interactive session
func (ns *NodeSession) runShell(callback ShellCreatedCallback) error {
	return ns.interactiveSession(func(s *ssh.Session, shell io.ReadWriteCloser) error {
		// start the shell on the server:
		if err := s.Shell(); err != nil {
			return trace.Wrap(err)
		}
		// call the client-supplied callback
		if callback != nil {
			exit, err := callback(shell)
			if exit {
				return trace.Wrap(err)
			}
		}
		return nil
	})
}

// runCommand executes a given command either in interactive (with terminal attached)
// or non-intractive mode
func (ns *NodeSession) runCommand(cmd []string, callback ShellCreatedCallback, interactive bool) error {
	// stdin is not a terminal? refuse to allocate terminal on the server and go back
	// to "non-interactive":
	if interactive && ns.stdin == os.Stdin && !term.IsTerminal(os.Stdin.Fd()) {
		interactive = false
		fmt.Fprintf(os.Stderr, "TTY will not be allocated on the server because stdin is not a terminal\n")
	}

	// interactive session:
	if interactive {
		return ns.interactiveSession(func(s *ssh.Session, shell io.ReadWriteCloser) error {
			err := s.Start(strings.Join(cmd, " "))
			if err != nil {
				return trace.Wrap(err)
			}
			if callback != nil {
				exit, err := callback(shell)
				if exit {
					return trace.Wrap(err)
				}
			}
			return nil
		})
	}
	// non-interactive session:
	return ns.regularSession(func(s *ssh.Session) error {
		return s.Run(strings.Join(cmd, " "))
	})
}

// watchSignals register UNIX signal handlers and properly terminates a remote shell session
// must be called as a goroutine right after a remote shell is created
func (ns *NodeSession) watchSignals(shell io.Writer) {
	exitSignals := make(chan os.Signal, 1)
	// catch SIGTERM
	signal.Notify(exitSignals, syscall.SIGTERM)
	go func() {
		defer ns.closer.Close()
		<-exitSignals
	}()
	// Catch Ctrl-C signal
	ctrlCSignal := make(chan os.Signal, 1)
	signal.Notify(ctrlCSignal, syscall.SIGINT)
	go func() {
		for {
			<-ctrlCSignal
			_, err := shell.Write([]byte{3})
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}()
	// Catch Ctrl-Z signal
	ctrlZSignal := make(chan os.Signal, 1)
	signal.Notify(ctrlZSignal, syscall.SIGTSTP)
	go func() {
		for {
			<-ctrlZSignal
			_, err := shell.Write([]byte{26})
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}()
}

// pipeInOut launches two goroutines: one to pipe the local input into the remote shell,
// and another to pipe the output of the remote shell into the local output
func (ns *NodeSession) pipeInOut(shell io.ReadWriteCloser) {
	// copy from the remote shell to the local output
	go func() {
		defer ns.closer.Close()
		_, err := io.Copy(ns.stdout, shell)
		if err != nil {
			log.Errorf(err.Error())
		}
	}()
	// copy from the local input to the remote shell:
	go func() {
		defer ns.closer.Close()
		buf := make([]byte, 128)
		for {
			n, err := ns.stdin.Read(buf)
			if err != nil {
				fmt.Println(trace.Wrap(err))
				return
			}
			if n > 0 {
				_, err = shell.Write(buf[:n])
				if err != nil {
					ns.ExitMsg = err.Error()
					return
				}
			}
		}
	}()
}
<|endoftext|>


<commit_before>package env_strings

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

const (
	ENV_STRINGS_KEY = "ENV_STRINGS"
	ENV_STRINGS_EXT = ".env"

	ENV_STRINGS_CONF = "/etc/env_strings.conf"

	ENV_STRINGS_CONFIG_KEY = "ENV_STRINGS_CONF"
)

const (
	STORAGE_REDIS = "redis"
)

type EnvStringConfig struct {
	Storages []StorageConfig `json:"storages"`
}

type StorageConfig struct {
	Engine  string                 `json:"engine"`
	Options map[string]interface{} `json:"options"`
}

type option func(envStrings *EnvStrings)

type EnvStrings struct {
	envName   string
	envExt    string
	tmplFuncs *TemplateFuncs

	configFile string

	envConfig EnvStringConfig
}

func FuncMap(name string, function interface{}) option {
	return func(e *EnvStrings) {
		e.RegisterFunc(name, function)
	}
}

func EnvStringsConfig(fileName string) option {
	return func(e *EnvStrings) {
		e.configFile = fileName
	}
}

func NewEnvStrings(envName string, envExt string, opts ...option) *EnvStrings {
	if envName == "" {
		panic("env_strings: env name could not be empty")
	}

	envStrings := &EnvStrings{
		envName:    envName,
		envExt:     envExt,
		configFile: ENV_STRINGS_CONF,
		tmplFuncs:  NewTemplateFuncs(),
	}

	if opts != nil && len(opts) > 0 {
		for _, opt := range opts {
			opt(envStrings)
		}
	}

	envStringsConf := os.Getenv(ENV_STRINGS_CONFIG_KEY)
	if envStringsConf != "" {
		envStrings.configFile = envStringsConf
	}

	if envStrings.configFile != "" {
		if err := envStrings.loadConfig(envStrings.configFile); err != nil {
			if !os.IsNotExist(err) {
				panic(err)
			} else {
				return envStrings
			}
		}

		if envStrings.envConfig.Storages != nil {
			for _, storageConf := range envStrings.envConfig.Storages {
				switch storageConf.Engine {
				case STORAGE_REDIS:
					{
						extFucnRedis := NewExtFuncsRedis(storageConf.Options)
						redisFuncs := extFucnRedis.GetFuncs()

						if redisFuncs == nil {
							panic("ext funcs of redis is nil")
						}

						for funcName, fn := range redisFuncs {
							if err := envStrings.RegisterFunc(funcName, fn); err != nil {
								panic(err)
							}
						}
					}
				default:
					{
						panic("unknown storage type")
					}
				}
			}
		}
	}
	return envStrings
}

func (p *EnvStrings) Execute(str string) (ret string, err error) {
	return p.ExecuteWith(str, nil)
}

func (p *EnvStrings) ExecuteWith(str string, envValues map[string]interface{}) (ret string, err error) {
	strEnvFiles := os.Getenv(p.envName)

	envFiles := strings.Split(strEnvFiles, ";")

	if len(envFiles) == 1 && len(envFiles[0]) == 0 {
		envFiles = nil
	}

	if envValues == nil {
		envValues = make(map[string]interface{})
	}

	for _, envFile := range envFiles {

		prefix := filepath.Base(envFile)

		prefix = filepath.Dir(prefix)

		err = p.loadEnv(prefix, []string{envFile}, envValues)

		if err != nil {
			return
		}
	}

	var tpl *template.Template

	if tpl, err = template.New("tmpl:" + p.envName).Funcs(p.tmplFuncs.GetFuncMaps(p.envName)).Option("missingkey=error").Parse(str); err != nil {
		return
	}

	var buf bytes.Buffer
	if err = tpl.Execute(&buf, envValues); err != nil {
		return
	}

	ret = buf.String()

	return
}

func Execute(str string) (ret string, err error) {
	envStrings := NewEnvStrings(ENV_STRINGS_KEY, ENV_STRINGS_EXT)
	return envStrings.Execute(str)
}

func ExecuteWith(str string, envValues map[string]interface{}) (ret string, err error) {
	envStrings := NewEnvStrings(ENV_STRINGS_KEY, ENV_STRINGS_EXT)
	return envStrings.ExecuteWith(str, envValues)
}

func (p *EnvStrings) RegisterFunc(name string, function interface{}) (err error) {
	return p.tmplFuncs.Register(name, function)
}

func (p *EnvStrings) FuncUsageStatic() map[string][]FuncStaticItem {
	return funcStatics
}

func (p *EnvStrings) loadEnv(prefix string, files []string, envs map[string]interface{}) (err error) {

	for _, path := range files {

		var fi os.FileInfo
		fi, err = os.Stat(path)
		if err != nil {
			return
		}

		if strings.HasPrefix(fi.Name(), ".") {
			continue
		}

		if fi.IsDir() {

			baseName := filepath.Base(path)

			if strings.HasPrefix(baseName, ".") {
				continue
			}

			var fis []os.FileInfo
			fis, err = ioutil.ReadDir(path)

			if err != nil {
				return
			}

			var nextfiles []string

			for _, f := range fis {
				nextfiles = append(nextfiles, filepath.Join(path, f.Name()))
			}

			var nextENVs map[string]interface{}

			if envs == nil {
				envs = make(map[string]interface{})
			}

			preEnvs, exist := envs[baseName]
			if !exist {
				nextENVs = make(map[string]interface{})
				envs[baseName] = nextENVs
			} else {
				nextENVs = preEnvs.(map[string]interface{})
			}

			err = p.loadEnv(prefix, nextfiles, nextENVs)
			if err != nil {
				return
			}

			continue
		}

		baseName := strings.TrimSuffix(fi.Name(), p.envExt)

		fileEnvs, err := p.loadEnvFile(path)

		if err != nil {
			return err
		}

		if envs == nil {
			envs = make(map[string]interface{})
		}

		envs[baseName] = fileEnvs
	}

	return
}

func (p *EnvStrings) loadEnvFile(filename string) (ret map[string]interface{}, err error) {

	if ext := filepath.Ext(filename); ext == p.envExt {

		var data []byte
		data, err = ioutil.ReadFile(filename)
		if err != nil {
			return
		}

		r := make(map[string]interface{})

		err = json.Unmarshal(data, &r)
		if err != nil {
			return
		}

		ret = r
	}

	return
}

func (p *EnvStrings) loadConfig(fileName string) (err error) {
	if _, err = os.Stat(fileName); err != nil {
		return
	}

	var data []byte
	if data, err = ioutil.ReadFile(fileName); err != nil {
		return
	}

	conf := EnvStringConfig{}

	if err = json.Unmarshal(data, &conf); err != nil {
		return
	}

	p.envConfig = conf

	return
}
<commit_msg>add ENV_STRING_DEBUG for debug<commit_after>package env_strings

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

const (
	ENV_STRINGS_KEY = "ENV_STRINGS"
	ENV_STRINGS_EXT = ".env"

	ENV_STRINGS_CONF = "/etc/env_strings.conf"

	ENV_STRINGS_CONFIG_KEY = "ENV_STRINGS_CONF"
)

const (
	STORAGE_REDIS = "redis"
)

type EnvStringConfig struct {
	Storages []StorageConfig `json:"storages"`
}

type StorageConfig struct {
	Engine  string                 `json:"engine"`
	Options map[string]interface{} `json:"options"`
}

type option func(envStrings *EnvStrings)

type EnvStrings struct {
	envName   string
	envExt    string
	tmplFuncs *TemplateFuncs

	configFile string

	envConfig EnvStringConfig
}

func FuncMap(name string, function interface{}) option {
	return func(e *EnvStrings) {
		e.RegisterFunc(name, function)
	}
}

func EnvStringsConfig(fileName string) option {
	return func(e *EnvStrings) {
		e.configFile = fileName
	}
}

func NewEnvStrings(envName string, envExt string, opts ...option) *EnvStrings {
	if envName == "" {
		panic("env_strings: env name could not be empty")
	}

	envStrings := &EnvStrings{
		envName:    envName,
		envExt:     envExt,
		configFile: ENV_STRINGS_CONF,
		tmplFuncs:  NewTemplateFuncs(),
	}

	if opts != nil && len(opts) > 0 {
		for _, opt := range opts {
			opt(envStrings)
		}
	}

	envStringsConf := os.Getenv(ENV_STRINGS_CONFIG_KEY)
	if envStringsConf != "" {
		envStrings.configFile = envStringsConf
	}

	if envStrings.configFile != "" {
		if err := envStrings.loadConfig(envStrings.configFile); err != nil {
			if !os.IsNotExist(err) {
				panic(err)
			} else {
				return envStrings
			}
		}

		if envStrings.envConfig.Storages != nil {
			for _, storageConf := range envStrings.envConfig.Storages {
				switch storageConf.Engine {
				case STORAGE_REDIS:
					{
						extFucnRedis := NewExtFuncsRedis(storageConf.Options)
						redisFuncs := extFucnRedis.GetFuncs()

						if redisFuncs == nil {
							panic("ext funcs of redis is nil")
						}

						for funcName, fn := range redisFuncs {
							if err := envStrings.RegisterFunc(funcName, fn); err != nil {
								panic(err)
							}
						}
					}
				default:
					{
						panic("unknown storage type")
					}
				}
			}
		}
	}
	return envStrings
}

func (p *EnvStrings) Execute(str string) (ret string, err error) {
	return p.ExecuteWith(str, nil)
}

func (p *EnvStrings) ExecuteWith(str string, envValues map[string]interface{}) (ret string, err error) {
	strEnvFiles := os.Getenv(p.envName)

	envFiles := strings.Split(strEnvFiles, ";")

	if len(envFiles) == 1 && len(envFiles[0]) == 0 {
		envFiles = nil
	}

	if envValues == nil {
		envValues = make(map[string]interface{})
	}

	for _, envFile := range envFiles {

		prefix := filepath.Base(envFile)

		prefix = filepath.Dir(prefix)

		err = p.loadEnv(prefix, []string{envFile}, envValues)

		if err != nil {
			return
		}
	}

	if os.Getenv("ENV_STRING_DEBUG") == "true" {
		debugData, _ := json.MarshalIndent(envValues, "", "    ")
		fmt.Println(string(debugData))
	}

	var tpl *template.Template

	if tpl, err = template.New("tmpl:" + p.envName).Funcs(p.tmplFuncs.GetFuncMaps(p.envName)).Option("missingkey=error").Parse(str); err != nil {
		return
	}

	var buf bytes.Buffer
	if err = tpl.Execute(&buf, envValues); err != nil {
		return
	}

	ret = buf.String()

	return
}

func Execute(str string) (ret string, err error) {
	envStrings := NewEnvStrings(ENV_STRINGS_KEY, ENV_STRINGS_EXT)
	return envStrings.Execute(str)
}

func ExecuteWith(str string, envValues map[string]interface{}) (ret string, err error) {
	envStrings := NewEnvStrings(ENV_STRINGS_KEY, ENV_STRINGS_EXT)
	return envStrings.ExecuteWith(str, envValues)
}

func (p *EnvStrings) RegisterFunc(name string, function interface{}) (err error) {
	return p.tmplFuncs.Register(name, function)
}

func (p *EnvStrings) FuncUsageStatic() map[string][]FuncStaticItem {
	return funcStatics
}

func (p *EnvStrings) loadEnv(prefix string, files []string, envs map[string]interface{}) (err error) {

	for _, path := range files {

		var fi os.FileInfo
		fi, err = os.Stat(path)
		if err != nil {
			return
		}

		if strings.HasPrefix(fi.Name(), ".") {
			continue
		}

		if fi.IsDir() {

			baseName := filepath.Base(path)

			if strings.HasPrefix(baseName, ".") {
				continue
			}

			var fis []os.FileInfo
			fis, err = ioutil.ReadDir(path)

			if err != nil {
				return
			}

			var nextfiles []string

			for _, f := range fis {
				nextfiles = append(nextfiles, filepath.Join(path, f.Name()))
			}

			var nextENVs map[string]interface{}

			if envs == nil {
				envs = make(map[string]interface{})
			}

			preEnvs, exist := envs[baseName]
			if !exist {
				nextENVs = make(map[string]interface{})
				envs[baseName] = nextENVs
			} else {
				nextENVs = preEnvs.(map[string]interface{})
			}

			err = p.loadEnv(prefix, nextfiles, nextENVs)
			if err != nil {
				return
			}

			continue
		}

		baseName := strings.TrimSuffix(fi.Name(), p.envExt)

		var fileEnvs map[string]interface{}
		fileEnvs, err = p.loadEnvFile(path)

		if err != nil {
			return err
		}

		if envs == nil {
			envs = make(map[string]interface{})
		}

		existEnvs, exist := envs[baseName]

		if exist {
			err = mergeMaps(existEnvs, fileEnvs)
			if err != nil {
				err = fmt.Errorf("merge same env key's values failure, file: %s, error: %s", path, err.Error())
				return
			}

		} else {
			envs[baseName] = fileEnvs
		}
	}

	return
}

func mergeMaps(vA, vB interface{}) (err error) {
	vAMap, okA := vA.(map[string]interface{})
	vBMap, okB := vB.(map[string]interface{})

	if okA && okB {
		for k, valB := range vBMap {
			valA, exist := vAMap[k]
			if !exist {
				vAMap[k] = valB
			} else {
				err = mergeMaps(valA, valB)
				if err != nil {
					return
				}
			}
		}

		return
	}

	err = fmt.Errorf("could not merge different struct values")

	return
}

func (p *EnvStrings) loadEnvFile(filename string) (ret map[string]interface{}, err error) {

	if ext := filepath.Ext(filename); ext == p.envExt {

		var data []byte
		data, err = ioutil.ReadFile(filename)
		if err != nil {
			return
		}

		r := make(map[string]interface{})

		err = json.Unmarshal(data, &r)
		if err != nil {
			return
		}

		ret = r
	}

	return
}

func (p *EnvStrings) loadConfig(fileName string) (err error) {
	if _, err = os.Stat(fileName); err != nil {
		return
	}

	var data []byte
	if data, err = ioutil.ReadFile(fileName); err != nil {
		return
	}

	conf := EnvStringConfig{}

	if err = json.Unmarshal(data, &conf); err != nil {
		return
	}

	p.envConfig = conf

	return
}
<|endoftext|>


<commit_before>// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package plugins allows users to operate on statistics recorded for each circuit operation.
// Plugins should be careful to be lightweight as they will be called frequently.
package plugins

import (
	"strings"
	"time"

	metricCollector "github.com/afex/hystrix-go/hystrix/metric_collector"
	"github.com/uber-go/tally"
)

// M3Collector fulfills the metricCollector interface allowing users to ship circuit
// stats to a m3 backend. To use users must call InitializeM3Collector before
// circuits are started. Then register NewM3Collector with metricCollector.Registry.Register(NewM3Collector).
type M3Collector struct {
	scope                   tally.Scope
	attemptsPrefix          string
	errorsPrefix            string
	successesPrefix         string
	failuresPrefix          string
	rejectsPrefix           string
	shortCircuitsPrefix     string
	timeoutsPrefix          string
	fallbackSuccessesPrefix string
	fallbackFailuresPrefix  string
	totalDurationPrefix     string
	runDurationPrefix       string
}

// M3CollectorClient provides configuration that the m3 client will need.
type M3CollectorClient struct {
	scope tally.Scope
}

// InitializeM3Collector creates the connection to the m3
func InitializeM3Collector(scope tally.Scope) *M3CollectorClient {
	return &M3CollectorClient{
		scope: scope,
	}

}

// NewM3Collector creates a collector for a specific circuit. The
// prefix given to this circuit will be {circuit_name}.circuitbreaker.{metric}...
// Circuits with "/" in their names will have them replaced with ".".
func (m *M3CollectorClient) NewM3Collector(name string) metricCollector.MetricCollector {
	name = strings.Replace(name, "/", "-", -1)
	name = strings.Replace(name, ":", "-", -1)
	name = strings.Replace(name, ".", "-", -1)
	return &M3Collector{
		scope:                   m.scope,
		attemptsPrefix:          name + ".circuitbreaker" + ".attempts",
		errorsPrefix:            name + ".circuitbreaker" + ".errors",
		successesPrefix:         name + ".circuitbreaker" + ".successes",
		failuresPrefix:          name + ".circuitbreaker" + ".failures",
		rejectsPrefix:           name + ".circuitbreaker" + ".rejects",
		shortCircuitsPrefix:     name + ".circuitbreaker" + ".shortCircuits",
		timeoutsPrefix:          name + ".circuitbreaker" + ".timeouts",
		fallbackSuccessesPrefix: name + ".circuitbreaker" + ".fallbackSuccesses",
		fallbackFailuresPrefix:  name + ".circuitbreaker" + ".fallbackFailures",
		totalDurationPrefix:     name + ".circuitbreaker" + ".totalDuration",
		runDurationPrefix:       name + ".circuitbreaker" + ".runDuration",
	}
}

func (g *M3Collector) incrementCounterMetric(prefix string, i float64) {
	if i == 0 {
		return
	}
	c := g.scope.Counter(prefix)
	c.Inc(int64(i))
}

func (g *M3Collector) updateTimerMetric(prefix string, dur time.Duration) {
	c := g.scope.Timer(prefix)
	c.Record(dur)
}

// Update is a callback by hystrix lib to relay the metrics to m3
func (g *M3Collector) Update(r metricCollector.MetricResult) {
	g.incrementCounterMetric(g.attemptsPrefix, r.Attempts)
	g.incrementCounterMetric(g.errorsPrefix, r.Errors)
	g.incrementCounterMetric(g.successesPrefix, r.Successes)
	g.incrementCounterMetric(g.failuresPrefix, r.Failures)
	g.incrementCounterMetric(g.rejectsPrefix, r.Rejects)
	g.incrementCounterMetric(g.shortCircuitsPrefix, r.ShortCircuits)
	g.incrementCounterMetric(g.timeoutsPrefix, r.Timeouts)
	g.incrementCounterMetric(g.fallbackSuccessesPrefix, r.FallbackSuccesses)
	g.incrementCounterMetric(g.fallbackFailuresPrefix, r.FallbackFailures)
	g.updateTimerMetric(g.totalDurationPrefix, r.TotalDuration)
	g.updateTimerMetric(g.runDurationPrefix, r.RunDuration)
}

// Reset is a noop operation in this collector.
func (g *M3Collector) Reset() {}
<commit_msg>Circuit breaker metrics grouping by name (#595)<commit_after>// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package plugins allows users to operate on statistics recorded for each circuit operation.
// Plugins should be careful to be lightweight as they will be called frequently.
package plugins

import (
	"strings"
	"time"

	metricCollector "github.com/afex/hystrix-go/hystrix/metric_collector"
	"github.com/uber-go/tally"
)

// M3Collector fulfills the metricCollector interface allowing users to ship circuit
// stats to a m3 backend. To use users must call InitializeM3Collector before
// circuits are started. Then register NewM3Collector with metricCollector.Registry.Register(NewM3Collector).
type M3Collector struct {
	scope                   tally.Scope
	attemptsPrefix          string
	errorsPrefix            string
	successesPrefix         string
	failuresPrefix          string
	rejectsPrefix           string
	shortCircuitsPrefix     string
	timeoutsPrefix          string
	fallbackSuccessesPrefix string
	fallbackFailuresPrefix  string
	totalDurationPrefix     string
	runDurationPrefix       string
}

// M3CollectorClient provides configuration that the m3 client will need.
type M3CollectorClient struct {
	scope tally.Scope
}

// InitializeM3Collector creates the connection to the m3
func InitializeM3Collector(scope tally.Scope) *M3CollectorClient {
	return &M3CollectorClient{
		scope: scope,
	}

}

// NewM3Collector creates a collector for a specific circuit. The
// prefix given to this circuit will be circuitbreaker.{metric}...
// Circuits with "/" in their names will have them replaced with ".".
func (m *M3CollectorClient) NewM3Collector(name string) metricCollector.MetricCollector {
	name = strings.Replace(name, "/", "-", -1)
	name = strings.Replace(name, ":", "-", -1)
	name = strings.Replace(name, ".", "-", -1)
	tagMap := map[string]string{"emitter-name": name}
	return &M3Collector{
		scope:                   m.scope.Tagged(tagMap),
		attemptsPrefix:          "circuitbreaker.attempts",
		errorsPrefix:            "circuitbreaker.errors",
		successesPrefix:         "circuitbreaker.successes",
		failuresPrefix:          "circuitbreaker.failures",
		rejectsPrefix:           "circuitbreaker.rejects",
		shortCircuitsPrefix:     "circuitbreaker.shortCircuits",
		timeoutsPrefix:          "circuitbreaker.timeouts",
		fallbackSuccessesPrefix: "circuitbreaker.fallbackSuccesses",
		fallbackFailuresPrefix:  "circuitbreaker.fallbackFailures",
		totalDurationPrefix:     "circuitbreaker.totalDuration",
		runDurationPrefix:       "circuitbreaker.runDuration",
	}
}

func (g *M3Collector) incrementCounterMetric(prefix string, i float64) {
	if i == 0 {
		return
	}
	c := g.scope.Counter(prefix)
	c.Inc(int64(i))
}

func (g *M3Collector) updateTimerMetric(prefix string, dur time.Duration) {
	c := g.scope.Timer(prefix)
	c.Record(dur)
}

// Update is a callback by hystrix lib to relay the metrics to m3
func (g *M3Collector) Update(r metricCollector.MetricResult) {
	g.incrementCounterMetric(g.attemptsPrefix, r.Attempts)
	g.incrementCounterMetric(g.errorsPrefix, r.Errors)
	g.incrementCounterMetric(g.successesPrefix, r.Successes)
	g.incrementCounterMetric(g.failuresPrefix, r.Failures)
	g.incrementCounterMetric(g.rejectsPrefix, r.Rejects)
	g.incrementCounterMetric(g.shortCircuitsPrefix, r.ShortCircuits)
	g.incrementCounterMetric(g.timeoutsPrefix, r.Timeouts)
	g.incrementCounterMetric(g.fallbackSuccessesPrefix, r.FallbackSuccesses)
	g.incrementCounterMetric(g.fallbackFailuresPrefix, r.FallbackFailures)
	g.updateTimerMetric(g.totalDurationPrefix, r.TotalDuration)
	g.updateTimerMetric(g.runDurationPrefix, r.RunDuration)
}

// Reset is a noop operation in this collector.
func (g *M3Collector) Reset() {}
<|endoftext|>