<commit_before>// Copyright 2015 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package variable

import (
	"github.com/pingcap/tidb/context"
)

// SessionVars is to handle user-defined or global variables in current session.
type SessionVars struct {
	// user-defined variables
	Users map[string]string
	// system variables
	Systems map[string]string
	// prepared statement
	PreparedStmts map[uint32]interface{}

	PreparedStmtNameToID map[string]uint32
	// prepared statement auto increment id
	preparedStmtID uint32

	// following variables are special for current session
	Status       uint16
	LastInsertID uint64
	AffectedRows uint64

	// Client capability
	ClientCapability uint32

	// Connection ID
	ConnectionID uint64

	// Found rows
	FoundRows uint64

	// Current user
	User string
}

// sessionVarsKeyType is a dummy type to avoid naming collision in context.
type sessionVarsKeyType int

// String defines a Stringer function for debugging and pretty printing.
func (k sessionVarsKeyType) String() string {
	return "session_vars"
}

const sessionVarsKey sessionVarsKeyType = 0

// BindSessionVars creates a session vars object and binds it to context.
func BindSessionVars(ctx context.Context) {
	v := &SessionVars{
		Users:                make(map[string]string),
		Systems:              make(map[string]string),
		PreparedStmts:        make(map[uint32]interface{}),
		PreparedStmtNameToID: make(map[string]uint32),
	}

	ctx.SetValue(sessionVarsKey, v)
}

// GetSessionVars gets the session vars from context.
func GetSessionVars(ctx context.Context) *SessionVars {
	v, ok := ctx.Value(sessionVarsKey).(*SessionVars)
	if !ok {
		return nil
	}
	return v
}

// What character set should the server translate a statement to after receiving it?
// For this, the server uses the character_set_connection and collation_connection system variables.
// It converts statements sent by the client from character_set_client to character_set_connection
// (except for string literals that have an introducer such as _latin1 or _utf8).
// collation_connection is important for comparisons of literal strings.
// For comparisons of strings with column values, collation_connection does not matter because columns
// have their own collation, which has a higher collation precedence.
// See: https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html
func GetCharsetInfo(ctx context.Context) (charset, collation string) {
	sessionVars := GetSessionVars(ctx)
	charset = sessionVars.Systems["character_set_connection"]
	collation = sessionVars.Systems["collation_connection"]
	return
}

// SetLastInsertID saves the last insert id to the session context.
// TODO: we may store the result for last_insert_id sys var later.
func (s *SessionVars) SetLastInsertID(insertID uint64) {
	s.LastInsertID = insertID
}

// SetAffectedRows saves the affected rows to the session context.
func (s *SessionVars) SetAffectedRows(affectedRows uint64) {
	s.AffectedRows = affectedRows
}

// AddAffectedRows adds affected rows with the argument rows.
func (s *SessionVars) AddAffectedRows(rows uint64) {
	s.AffectedRows += rows
}

// AddFoundRows adds found rows with the argument rows.
func (s *SessionVars) AddFoundRows(rows uint64) {
	s.FoundRows += rows
}

// SetStatusFlag sets the session server status variable.
// If on is ture sets the flag in session status,
// otherwise removes the flag.
func (s *SessionVars) SetStatusFlag(flag uint16, on bool) {
	if on {
		s.Status |= flag
		return
	}
	s.Status &= (^flag)
}

// GetNextPreparedStmtID generates and returns the next session scope prepared statement id.
func (s *SessionVars) GetNextPreparedStmtID() uint32 {
	s.preparedStmtID++
	return s.preparedStmtID
}

// SetCurrentUser saves the current user to the session context.
func (s *SessionVars) SetCurrentUser(user string) {
	s.User = user
}
<commit_msg>session/variable: fix method comment.<commit_after>// Copyright 2015 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package variable

import (
	"github.com/pingcap/tidb/context"
)

// SessionVars is to handle user-defined or global variables in current session.
type SessionVars struct {
	// user-defined variables
	Users map[string]string
	// system variables
	Systems map[string]string
	// prepared statement
	PreparedStmts map[uint32]interface{}

	PreparedStmtNameToID map[string]uint32
	// prepared statement auto increment id
	preparedStmtID uint32

	// following variables are special for current session
	Status       uint16
	LastInsertID uint64
	AffectedRows uint64

	// Client capability
	ClientCapability uint32

	// Connection ID
	ConnectionID uint64

	// Found rows
	FoundRows uint64

	// Current user
	User string
}

// sessionVarsKeyType is a dummy type to avoid naming collision in context.
type sessionVarsKeyType int

// String defines a Stringer function for debugging and pretty printing.
func (k sessionVarsKeyType) String() string {
	return "session_vars"
}

const sessionVarsKey sessionVarsKeyType = 0

// BindSessionVars creates a session vars object and binds it to context.
func BindSessionVars(ctx context.Context) {
	v := &SessionVars{
		Users:                make(map[string]string),
		Systems:              make(map[string]string),
		PreparedStmts:        make(map[uint32]interface{}),
		PreparedStmtNameToID: make(map[string]uint32),
	}

	ctx.SetValue(sessionVarsKey, v)
}

// GetSessionVars gets the session vars from context.
func GetSessionVars(ctx context.Context) *SessionVars {
	v, ok := ctx.Value(sessionVarsKey).(*SessionVars)
	if !ok {
		return nil
	}
	return v
}

// GetCharsetInfo gets charset and collation for current context.
// What character set should the server translate a statement to after receiving it?
// For this, the server uses the character_set_connection and collation_connection system variables.
// It converts statements sent by the client from character_set_client to character_set_connection
// (except for string literals that have an introducer such as _latin1 or _utf8).
// collation_connection is important for comparisons of literal strings.
// For comparisons of strings with column values, collation_connection does not matter because columns
// have their own collation, which has a higher collation precedence.
// See: https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html
func GetCharsetInfo(ctx context.Context) (charset, collation string) {
	sessionVars := GetSessionVars(ctx)
	charset = sessionVars.Systems["character_set_connection"]
	collation = sessionVars.Systems["collation_connection"]
	return
}

// SetLastInsertID saves the last insert id to the session context.
// TODO: we may store the result for last_insert_id sys var later.
func (s *SessionVars) SetLastInsertID(insertID uint64) {
	s.LastInsertID = insertID
}

// SetAffectedRows saves the affected rows to the session context.
func (s *SessionVars) SetAffectedRows(affectedRows uint64) {
	s.AffectedRows = affectedRows
}

// AddAffectedRows adds affected rows with the argument rows.
func (s *SessionVars) AddAffectedRows(rows uint64) {
	s.AffectedRows += rows
}

// AddFoundRows adds found rows with the argument rows.
func (s *SessionVars) AddFoundRows(rows uint64) {
	s.FoundRows += rows
}

// SetStatusFlag sets the session server status variable.
// If on is ture sets the flag in session status,
// otherwise removes the flag.
func (s *SessionVars) SetStatusFlag(flag uint16, on bool) {
	if on {
		s.Status |= flag
		return
	}
	s.Status &= (^flag)
}

// GetNextPreparedStmtID generates and returns the next session scope prepared statement id.
func (s *SessionVars) GetNextPreparedStmtID() uint32 {
	s.preparedStmtID++
	return s.preparedStmtID
}

// SetCurrentUser saves the current user to the session context.
func (s *SessionVars) SetCurrentUser(user string) {
	s.User = user
}
<|endoftext|>


<commit_before>/*
Package firego is a REST client for Firebase (https://firebase.com).
*/
package firego

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	_url "net/url"
	"strings"
	"sync"
	"time"
)

// TimeoutDuration is the length of time any request will have to establish
// a connection and receive headers from Firebase before returning
// an ErrTimeout error
var TimeoutDuration = 30 * time.Second

var defaultRedirectLimit = 30

// ErrTimeout is an error type is that is returned if a request
// exceeds the TimeoutDuration configured
type ErrTimeout struct {
	error
}

// query parameter constants
const (
	authParam    = "auth"
	formatParam  = "format"
	shallowParam = "shallow"
	formatVal    = "export"
)

// Firebase represents a location in the cloud
type Firebase struct {
	url    string
	params _url.Values
	client *http.Client

	watchMtx     sync.Mutex
	watching     bool
	stopWatching chan struct{}
}

func sanitizeURL(url string) string {
	if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") {
		url = "https://" + url
	}

	if strings.HasSuffix(url, "/") {
		url = url[:len(url)-1]
	}

	return url
}

// Preserve headers on redirect
// See: https://github.com/golang/go/issues/4800
func redirectPreserveHeaders(req *http.Request, via []*http.Request) error {
	if len(via) == 0 {
		// No redirects
		return nil
	}

	if len(via) > defaultRedirectLimit {
		return fmt.Errorf("%d consecutive requests(redirects)", len(via))
	}

	// mutate the subsequent redirect requests with the first Header
	for key, val := range via[0].Header {
		req.Header[key] = val
	}
	return nil
}

// New creates a new Firebase reference
func New(url string) *Firebase {

	var tr *http.Transport
	tr = &http.Transport{
		DisableKeepAlives: true, // https://code.google.com/p/go/issues/detail?id=3514
		Dial: func(network, address string) (net.Conn, error) {
			start := time.Now()
			c, err := net.DialTimeout(network, address, TimeoutDuration)
			tr.ResponseHeaderTimeout = TimeoutDuration - time.Since(start)
			return c, err
		},
	}

	var client *http.Client
	client = &http.Client{
		Transport:     tr,
		CheckRedirect: redirectPreserveHeaders,
	}

	return &Firebase{
		url:          sanitizeURL(url),
		params:       _url.Values{},
		client:       client,
		stopWatching: make(chan struct{}),
	}
}

// String returns the string representation of the
// Firebase reference
func (fb *Firebase) String() string {
	return fb.url
}

// Child creates a new Firebase reference for the requested
// child with the same configuration as the parent
func (fb *Firebase) Child(child string) *Firebase {
	c := &Firebase{
		url:          fb.url + "/" + child,
		params:       _url.Values{},
		client:       fb.client,
		stopWatching: make(chan struct{}),
	}

	// making sure to manually copy the map items into a new
	// map to avoid modifying the map reference.
	for k, v := range fb.params {
		c.params[k] = v
	}
	return c
}

// Shallow limits the depth of the data returned when calling Value.
// If the data at the location is a JSON primitive (string, number or boolean),
// its value will be returned. If the data is a JSON object, the values
// for each key will be truncated to true.
//
// Reference https://www.firebase.com/docs/rest/api/#section-param-shallow
func (fb *Firebase) Shallow(v bool) {
	if v {
		fb.params.Set(shallowParam, "true")
	} else {
		fb.params.Del(shallowParam)
	}
}

// IncludePriority determines whether or not to ask Firebase
// for the values priority. By default, the priority is not returned
//
// Reference https://www.firebase.com/docs/rest/api/#section-param-format
func (fb *Firebase) IncludePriority(v bool) {
	if v {
		fb.params.Set(formatParam, formatVal)
	} else {
		fb.params.Del(formatParam)
	}
}

func (fb *Firebase) makeRequest(method string, body []byte) (*http.Request, error) {
	path := fb.url + "/.json"

	if len(fb.params) > 0 {
		path += "?" + fb.params.Encode()
	}
	return http.NewRequest(method, path, bytes.NewReader(body))
}

func (fb *Firebase) doRequest(method string, body []byte) ([]byte, error) {
	req, err := fb.makeRequest(method, body)
	if err != nil {
		return nil, err
	}

	resp, err := fb.client.Do(req)
	switch err := err.(type) {
	default:
		return nil, err
	case nil:
		// carry on

	case *_url.Error:
		// `http.Client.Do` will return a `url.Error` that wraps a `net.Error`
		// when exceeding it's `Transport`'s `ResponseHeadersTimeout`
		e1, ok := err.Err.(net.Error)
		if ok && e1.Timeout() {
			return nil, ErrTimeout{err}
		}

		return nil, err

	case net.Error:
		// `http.Client.Do` will return a `net.Error` directly when Dial times
		// out, or when the Client's RoundTripper otherwise returns an err
		if err.Timeout() {
			return nil, ErrTimeout{err}
		}

		return nil, err
	}

	defer resp.Body.Close()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/200 != 1 {
		return nil, errors.New(string(respBody))
	}
	return respBody, nil
}
<commit_msg>Retry request with new Location on 307 response<commit_after>/*
Package firego is a REST client for Firebase (https://firebase.com).
*/
package firego

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	_url "net/url"
	"strings"
	"sync"
	"time"
)

// TimeoutDuration is the length of time any request will have to establish
// a connection and receive headers from Firebase before returning
// an ErrTimeout error
var TimeoutDuration = 30 * time.Second

var defaultRedirectLimit = 30

// ErrTimeout is an error type is that is returned if a request
// exceeds the TimeoutDuration configured
type ErrTimeout struct {
	error
}

// query parameter constants
const (
	authParam    = "auth"
	formatParam  = "format"
	shallowParam = "shallow"
	formatVal    = "export"
)

// Firebase represents a location in the cloud
type Firebase struct {
	url    string
	params _url.Values
	client *http.Client

	watchMtx     sync.Mutex
	watching     bool
	stopWatching chan struct{}
}

func sanitizeURL(url string) string {
	if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") {
		url = "https://" + url
	}

	if strings.HasSuffix(url, "/") {
		url = url[:len(url)-1]
	}

	return url
}

// Preserve headers on redirect
// See: https://github.com/golang/go/issues/4800
func redirectPreserveHeaders(req *http.Request, via []*http.Request) error {
	if len(via) == 0 {
		// No redirects
		return nil
	}

	if len(via) > defaultRedirectLimit {
		return fmt.Errorf("%d consecutive requests(redirects)", len(via))
	}

	// mutate the subsequent redirect requests with the first Header
	for key, val := range via[0].Header {
		req.Header[key] = val
	}
	return nil
}

// New creates a new Firebase reference
func New(url string) *Firebase {

	var tr *http.Transport
	tr = &http.Transport{
		DisableKeepAlives: true, // https://code.google.com/p/go/issues/detail?id=3514
		Dial: func(network, address string) (net.Conn, error) {
			start := time.Now()
			c, err := net.DialTimeout(network, address, TimeoutDuration)
			tr.ResponseHeaderTimeout = TimeoutDuration - time.Since(start)
			return c, err
		},
	}

	var client *http.Client
	client = &http.Client{
		Transport:     tr,
		CheckRedirect: redirectPreserveHeaders,
	}

	return &Firebase{
		url:          sanitizeURL(url),
		params:       _url.Values{},
		client:       client,
		stopWatching: make(chan struct{}),
	}
}

// String returns the string representation of the
// Firebase reference
func (fb *Firebase) String() string {
	return fb.url
}

// Child creates a new Firebase reference for the requested
// child with the same configuration as the parent
func (fb *Firebase) Child(child string) *Firebase {
	c := &Firebase{
		url:          fb.url + "/" + child,
		params:       _url.Values{},
		client:       fb.client,
		stopWatching: make(chan struct{}),
	}

	// making sure to manually copy the map items into a new
	// map to avoid modifying the map reference.
	for k, v := range fb.params {
		c.params[k] = v
	}
	return c
}

// Shallow limits the depth of the data returned when calling Value.
// If the data at the location is a JSON primitive (string, number or boolean),
// its value will be returned. If the data is a JSON object, the values
// for each key will be truncated to true.
//
// Reference https://www.firebase.com/docs/rest/api/#section-param-shallow
func (fb *Firebase) Shallow(v bool) {
	if v {
		fb.params.Set(shallowParam, "true")
	} else {
		fb.params.Del(shallowParam)
	}
}

// IncludePriority determines whether or not to ask Firebase
// for the values priority. By default, the priority is not returned
//
// Reference https://www.firebase.com/docs/rest/api/#section-param-format
func (fb *Firebase) IncludePriority(v bool) {
	if v {
		fb.params.Set(formatParam, formatVal)
	} else {
		fb.params.Del(formatParam)
	}
}

func (fb *Firebase) makeRequest(method string, body []byte) (*http.Request, error) {
	path := fb.url + "/.json"

	if len(fb.params) > 0 {
		path += "?" + fb.params.Encode()
	}
	return http.NewRequest(method, path, bytes.NewReader(body))
}

func (fb *Firebase) doRequest(method string, body []byte) ([]byte, error) {
	req, err := fb.makeRequest(method, body)
	if err != nil {
		return nil, err
	}

	resp, err := fb.client.Do(req)
	switch err := err.(type) {
	default:
		return nil, err
	case nil:
		// check for 307 redirect
		if resp.StatusCode == http.StatusTemporaryRedirect {
			loc, err := resp.Location()
			if err != nil {
				return nil, err
			}

			fb.url = strings.Split(loc.String(), "/.json")[0]
			return fb.doRequest(method, body)
		}

	case *_url.Error:
		// `http.Client.Do` will return a `url.Error` that wraps a `net.Error`
		// when exceeding it's `Transport`'s `ResponseHeadersTimeout`
		e1, ok := err.Err.(net.Error)
		if ok && e1.Timeout() {
			return nil, ErrTimeout{err}
		}

		return nil, err

	case net.Error:
		// `http.Client.Do` will return a `net.Error` directly when Dial times
		// out, or when the Client's RoundTripper otherwise returns an err
		if err.Timeout() {
			return nil, ErrTimeout{err}
		}

		return nil, err
	}

	defer resp.Body.Close()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/200 != 1 {
		return nil, errors.New(string(respBody))
	}
	return respBody, nil
}
<|endoftext|>


<commit_before>package htlcswitch

import (
	"encoding/binary"
	"io"

	"github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/lnwire"
)

// NetworkHop indicates the blockchain network that is intended to be the next
// hop for a forwarded HTLC. The existence of this field within the
// ForwardingInfo struct enables the ability for HTLC to cross chain-boundaries
// at will.
type NetworkHop uint8

const (
	// BitcoinHop denotes that an HTLC is to be forwarded along the Bitcoin
	// link with the specified short channel ID.
	BitcoinHop NetworkHop = iota

	// LitecoinHop denotes that an HTLC is to be forwarded along the
	// Litecoin link with the specified short channel ID.
	LitecoinHop
)

// String returns the string representation of the target NetworkHop.
func (c NetworkHop) String() string {
	switch c {
	case BitcoinHop:
		return "Bitcoin"
	case LitecoinHop:
		return "Litecoin"
	default:
		return "Kekcoin"
	}
}

var (
	// exitHop is a special "hop" which denotes that an incoming HTLC is
	// meant to pay finally to the receiving node.
	exitHop lnwire.ShortChannelID

	// sourceHop is a sentinel value denoting that an incoming HTLC is
	// initiated by our own switch.
	sourceHop lnwire.ShortChannelID
)

// ForwardingInfo contains all the information that is necessary to forward and
// incoming HTLC to the next hop encoded within a valid HopIterator instance.
// Forwarding links are to use this information to authenticate the information
// received within the incoming HTLC, to ensure that the prior hop didn't
// tamper with the end-to-end routing information at all.
type ForwardingInfo struct {
	// Network is the target blockchain network that the HTLC will travel
	// over next.
	Network NetworkHop

	// NextHop is the channel ID of the next hop. The received HTLC should
	// be forwarded to this particular channel in order to continue the
	// end-to-end route.
	NextHop lnwire.ShortChannelID

	// AmountToForward is the amount of milli-satoshis that the receiving
	// node should forward to the next hop.
	AmountToForward lnwire.MilliSatoshi

	// OutgoingCTLV is the specified value of the CTLV timelock to be used
	// in the outgoing HTLC.
	OutgoingCTLV uint32

	// TODO(roasbeef): modify sphinx logic to not just discard the
	// remaining bytes, instead should include the rest as excess
}

// HopIterator is an interface that abstracts away the routing information
// included in HTLC's which includes the entirety of the payment path of an
// HTLC. This interface provides two basic method which carry out: how to
// interpret the forwarding information encoded within the HTLC packet, and hop
// to encode the forwarding information for the _next_ hop.
type HopIterator interface {
	// ForwardingInstructions returns the set of fields that detail exactly
	// _how_ this hop should forward the HTLC to the next hop.
	// Additionally, the information encoded within the returned
	// ForwardingInfo is to be used by each hop to authenticate the
	// information given to it by the prior hop.
	ForwardingInstructions() ForwardingInfo

	// EncodeNextHop encodes the onion packet destined for the next hop
	// into the passed io.Writer.
	EncodeNextHop(w io.Writer) error

	// ExtractErrorEncrypter returns the ErrorEncrypter needed for this hop,
	// along with a failure code to signal if the decoding was successful.
	ExtractErrorEncrypter(ErrorEncrypterExtracter) (ErrorEncrypter,
		lnwire.FailCode)
}

// sphinxHopIterator is the Sphinx implementation of hop iterator which uses
// onion routing to encode the payment route  in such a way so that node might
// see only the next hop in the route..
type sphinxHopIterator struct {
	// ogPacket is the original packet from which the processed packet is
	// derived.
	ogPacket *sphinx.OnionPacket

	// processedPacket is the outcome of processing an onion packet. It
	// includes the information required to properly forward the packet to
	// the next hop.
	processedPacket *sphinx.ProcessedPacket
}

// makeSphinxHopIterator converts a processed packet returned from a sphinx
// router and converts it into an hop iterator for usage in the link.
func makeSphinxHopIterator(ogPacket *sphinx.OnionPacket,
	packet *sphinx.ProcessedPacket) *sphinxHopIterator {

	return &sphinxHopIterator{
		ogPacket:        ogPacket,
		processedPacket: packet,
	}
}

// A compile time check to ensure sphinxHopIterator implements the HopIterator
// interface.
var _ HopIterator = (*sphinxHopIterator)(nil)

// Encode encodes iterator and writes it to the writer.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
	return r.processedPacket.NextPacket.Encode(w)
}

// ForwardingInstructions returns the set of fields that detail exactly _how_
// this hop should forward the HTLC to the next hop.  Additionally, the
// information encoded within the returned ForwardingInfo is to be used by each
// hop to authenticate the information given to it by the prior hop.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ForwardingInstructions() ForwardingInfo {
	fwdInst := r.processedPacket.ForwardingInstructions

	var nextHop lnwire.ShortChannelID
	switch r.processedPacket.Action {
	case sphinx.ExitNode:
		nextHop = exitHop
	case sphinx.MoreHops:
		s := binary.BigEndian.Uint64(fwdInst.NextAddress[:])
		nextHop = lnwire.NewShortChanIDFromInt(s)
	}

	return ForwardingInfo{
		Network:         BitcoinHop,
		NextHop:         nextHop,
		AmountToForward: lnwire.MilliSatoshi(fwdInst.ForwardAmount),
		OutgoingCTLV:    fwdInst.OutgoingCltv,
	}
}

// ExtractErrorEncrypter decodes and returns the ErrorEncrypter for this hop,
// along with a failure code to signal if the decoding was successful. The
// ErrorEncrypter is used to encrypt errors back to the sender in the event that
// a payment fails.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ExtractErrorEncrypter(
	extracter ErrorEncrypterExtracter) (ErrorEncrypter, lnwire.FailCode) {

	return extracter(r.ogPacket)
}

// OnionProcessor is responsible for keeping all sphinx dependent parts inside
// and expose only decoding function. With such approach we give freedom for
// subsystems which wants to decode sphinx path to not be dependable from
// sphinx at all.
//
// NOTE: The reason for keeping decoder separated from hop iterator is too
// maintain the hop iterator abstraction. Without it the structures which using
// the hop iterator should contain sphinx router which makes their creations in
// tests dependent from the sphinx internal parts.
type OnionProcessor struct {
	router *sphinx.Router
}

// NewOnionProcessor creates new instance of decoder.
func NewOnionProcessor(router *sphinx.Router) *OnionProcessor {
	return &OnionProcessor{router}
}

// Start spins up the onion processor's sphinx router.
func (p *OnionProcessor) Start() error {
	return p.router.Start()
}

// Stop shutsdown the onion processor's sphinx router.
func (p *OnionProcessor) Stop() error {
	p.router.Stop()
	return nil
}

// DecodeHopIterator attempts to decode a valid sphinx packet from the passed io.Reader
// instance using the rHash as the associated data when checking the relevant
// MACs during the decoding process.
func (p *OnionProcessor) DecodeHopIterator(r io.Reader, rHash []byte,
	incomingCltv uint32) (HopIterator, lnwire.FailCode) {

	onionPkt := &sphinx.OnionPacket{}
	if err := onionPkt.Decode(r); err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			log.Errorf("unable to decode onion packet: %v", err)
			return nil, lnwire.CodeInvalidOnionKey
		}
	}

	// Attempt to process the Sphinx packet. We include the payment hash of
	// the HTLC as it's authenticated within the Sphinx packet itself as
	// associated data in order to thwart attempts a replay attacks. In the
	// case of a replay, an attacker is *forced* to use the same payment
	// hash twice, thereby losing their money entirely.
	sphinxPacket, err := p.router.ProcessOnionPacket(
		onionPkt, rHash, incomingCltv,
	)
	if err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionHMAC:
			return nil, lnwire.CodeInvalidOnionHmac
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			log.Errorf("unable to process onion packet: %v", err)
			return nil, lnwire.CodeInvalidOnionKey
		}
	}

	return makeSphinxHopIterator(onionPkt, sphinxPacket), lnwire.CodeNone
}

// DecodeHopIteratorRequest encapsulates all date necessary to process an onion
// packet, perform sphinx replay detection, and schedule the entry for garbage
// collection.
type DecodeHopIteratorRequest struct {
	OnionReader  io.Reader
	RHash        []byte
	IncomingCltv uint32
}

// DecodeHopIteratorResponse encapsulates the outcome of a batched sphinx onion
// processing.
type DecodeHopIteratorResponse struct {
	HopIterator HopIterator
	FailCode    lnwire.FailCode
}

// Result returns the (HopIterator, lnwire.FailCode) tuple, which should
// correspond to the index of a particular DecodeHopIteratorRequest.
//
// NOTE: The HopIterator should be considered invalid if the fail code is
// anything but lnwire.CodeNone.
func (r *DecodeHopIteratorResponse) Result() (HopIterator, lnwire.FailCode) {
	return r.HopIterator, r.FailCode
}

// DecodeHopIterators performs batched decoding and validation of incoming
// sphinx packets. For the same `id`, this method will return the same iterators
// and failcodes upon subsequent invocations.
//
// NOTE: In order for the responses to be valid, the caller must guarantee that
// the presented readers and rhashes *NEVER* deviate across invocations for the
// same id.
func (p *OnionProcessor) DecodeHopIterators(id []byte,
	reqs []DecodeHopIteratorRequest) ([]DecodeHopIteratorResponse, error) {

	var (
		batchSize = len(reqs)
		onionPkts = make([]sphinx.OnionPacket, batchSize)
		resps     = make([]DecodeHopIteratorResponse, batchSize)
	)

	tx := p.router.BeginTxn(id, batchSize)

	for i, req := range reqs {
		onionPkt := &onionPkts[i]
		resp := &resps[i]

		err := onionPkt.Decode(req.OnionReader)
		switch err {
		case nil:
			// success

		case sphinx.ErrInvalidOnionVersion:
			resp.FailCode = lnwire.CodeInvalidOnionVersion
			continue

		case sphinx.ErrInvalidOnionKey:
			resp.FailCode = lnwire.CodeInvalidOnionKey
			continue

		default:
			log.Errorf("unable to decode onion packet: %v", err)
			resp.FailCode = lnwire.CodeInvalidOnionKey
			continue
		}

		err = tx.ProcessOnionPacket(
			uint16(i), onionPkt, req.RHash, req.IncomingCltv,
		)
		switch err {
		case nil:
			// success

		case sphinx.ErrInvalidOnionVersion:
			resp.FailCode = lnwire.CodeInvalidOnionVersion
			continue

		case sphinx.ErrInvalidOnionHMAC:
			resp.FailCode = lnwire.CodeInvalidOnionHmac
			continue

		case sphinx.ErrInvalidOnionKey:
			resp.FailCode = lnwire.CodeInvalidOnionKey
			continue

		default:
			log.Errorf("unable to process onion packet: %v", err)
			resp.FailCode = lnwire.CodeInvalidOnionKey
			continue
		}
	}

	// With that batch created, we will now attempt to write the shared
	// secrets to disk. This operation will returns the set of indices that
	// were detected as replays, and the computed sphinx packets for all
	// indices that did not fail the above loop. Only indices that are not
	// in the replay set should be considered valid, as they are
	// opportunistically computed.
	packets, replays, err := tx.Commit()
	if err != nil {
		log.Errorf("unable to process onion packet batch %x: %v",
			id, err)

		// If we failed to commit the batch to the secret share log, we
		// will mark all not-yet-failed channels with a temporary
		// channel failure and exit since we cannot proceed.
		for i := range resps {
			resp := &resps[i]

			// Skip any indexes that already failed onion decoding.
			if resp.FailCode != lnwire.CodeNone {
				continue
			}

			log.Errorf("unable to process onion packet %x-%v",
				id, i)
			resp.FailCode = lnwire.CodeTemporaryChannelFailure
		}

		// TODO(conner): return real errors to caller so link can fail?
		return resps, err
	}

	// Otherwise, the commit was successful. Now we will post process any
	// remaining packets, additionally failing any that were included in the
	// replay set.
	for i := range resps {
		resp := &resps[i]

		// Skip any indexes that already failed onion decoding.
		if resp.FailCode != lnwire.CodeNone {
			continue
		}

		// If this index is contained in the replay set, mark it with a
		// temporary channel failure error code. We infer that the
		// offending error was due to a replayed packet because this
		// index was found in the replay set.
		if replays.Contains(uint16(i)) {
			log.Errorf("unable to process onion packet: %v",
				sphinx.ErrReplayedPacket)
			resp.FailCode = lnwire.CodeTemporaryChannelFailure
			continue
		}

		// Finally, construct a hop iterator from our processed sphinx
		// packet, simultaneously caching the original onion packet.
		resp.HopIterator = makeSphinxHopIterator(&onionPkts[i], &packets[i])
	}

	return resps, nil
}

// ExtractErrorEncrypter takes an io.Reader which should contain the onion
// packet as original received by a forwarding node and creates an
// ErrorEncrypter instance using the derived shared secret. In the case that en
// error occurs, a lnwire failure code detailing the parsing failure will be
// returned.
func (p *OnionProcessor) ExtractErrorEncrypter(onionPkt *sphinx.OnionPacket) (
	ErrorEncrypter, lnwire.FailCode) {

	onionObfuscator, err := sphinx.NewOnionErrorEncrypter(p.router,
		onionPkt.EphemeralKey)
	if err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionHMAC:
			return nil, lnwire.CodeInvalidOnionHmac
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			log.Errorf("unable to process onion packet: %v", err)
			return nil, lnwire.CodeInvalidOnionKey
		}
	}

	return &SphinxErrorEncrypter{
		OnionErrorEncrypter: onionObfuscator,
		ogPacket:            onionPkt,
	}, lnwire.CodeNone
}
<commit_msg>htlcswitch/iterator: extract error encrypter from ephemeral key<commit_after>package htlcswitch

import (
	"encoding/binary"
	"io"

	"github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/roasbeef/btcd/btcec"
)

// NetworkHop indicates the blockchain network that is intended to be the next
// hop for a forwarded HTLC. The existence of this field within the
// ForwardingInfo struct enables the ability for HTLC to cross chain-boundaries
// at will.
type NetworkHop uint8

const (
	// BitcoinHop denotes that an HTLC is to be forwarded along the Bitcoin
	// link with the specified short channel ID.
	BitcoinHop NetworkHop = iota

	// LitecoinHop denotes that an HTLC is to be forwarded along the
	// Litecoin link with the specified short channel ID.
	LitecoinHop
)

// String returns the string representation of the target NetworkHop.
func (c NetworkHop) String() string {
	switch c {
	case BitcoinHop:
		return "Bitcoin"
	case LitecoinHop:
		return "Litecoin"
	default:
		return "Kekcoin"
	}
}

var (
	// exitHop is a special "hop" which denotes that an incoming HTLC is
	// meant to pay finally to the receiving node.
	exitHop lnwire.ShortChannelID

	// sourceHop is a sentinel value denoting that an incoming HTLC is
	// initiated by our own switch.
	sourceHop lnwire.ShortChannelID
)

// ForwardingInfo contains all the information that is necessary to forward and
// incoming HTLC to the next hop encoded within a valid HopIterator instance.
// Forwarding links are to use this information to authenticate the information
// received within the incoming HTLC, to ensure that the prior hop didn't
// tamper with the end-to-end routing information at all.
type ForwardingInfo struct {
	// Network is the target blockchain network that the HTLC will travel
	// over next.
	Network NetworkHop

	// NextHop is the channel ID of the next hop. The received HTLC should
	// be forwarded to this particular channel in order to continue the
	// end-to-end route.
	NextHop lnwire.ShortChannelID

	// AmountToForward is the amount of milli-satoshis that the receiving
	// node should forward to the next hop.
	AmountToForward lnwire.MilliSatoshi

	// OutgoingCTLV is the specified value of the CTLV timelock to be used
	// in the outgoing HTLC.
	OutgoingCTLV uint32

	// TODO(roasbeef): modify sphinx logic to not just discard the
	// remaining bytes, instead should include the rest as excess
}

// HopIterator is an interface that abstracts away the routing information
// included in HTLC's which includes the entirety of the payment path of an
// HTLC. This interface provides two basic method which carry out: how to
// interpret the forwarding information encoded within the HTLC packet, and hop
// to encode the forwarding information for the _next_ hop.
type HopIterator interface {
	// ForwardingInstructions returns the set of fields that detail exactly
	// _how_ this hop should forward the HTLC to the next hop.
	// Additionally, the information encoded within the returned
	// ForwardingInfo is to be used by each hop to authenticate the
	// information given to it by the prior hop.
	ForwardingInstructions() ForwardingInfo

	// EncodeNextHop encodes the onion packet destined for the next hop
	// into the passed io.Writer.
	EncodeNextHop(w io.Writer) error

	// ExtractErrorEncrypter returns the ErrorEncrypter needed for this hop,
	// along with a failure code to signal if the decoding was successful.
	ExtractErrorEncrypter(ErrorEncrypterExtracter) (ErrorEncrypter,
		lnwire.FailCode)
}

// sphinxHopIterator is the Sphinx implementation of hop iterator which uses
// onion routing to encode the payment route  in such a way so that node might
// see only the next hop in the route..
type sphinxHopIterator struct {
	// ogPacket is the original packet from which the processed packet is
	// derived.
	ogPacket *sphinx.OnionPacket

	// processedPacket is the outcome of processing an onion packet. It
	// includes the information required to properly forward the packet to
	// the next hop.
	processedPacket *sphinx.ProcessedPacket
}

// makeSphinxHopIterator converts a processed packet returned from a sphinx
// router and converts it into an hop iterator for usage in the link.
func makeSphinxHopIterator(ogPacket *sphinx.OnionPacket,
	packet *sphinx.ProcessedPacket) *sphinxHopIterator {

	return &sphinxHopIterator{
		ogPacket:        ogPacket,
		processedPacket: packet,
	}
}

// A compile time check to ensure sphinxHopIterator implements the HopIterator
// interface.
var _ HopIterator = (*sphinxHopIterator)(nil)

// Encode encodes iterator and writes it to the writer.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
	return r.processedPacket.NextPacket.Encode(w)
}

// ForwardingInstructions returns the set of fields that detail exactly _how_
// this hop should forward the HTLC to the next hop.  Additionally, the
// information encoded within the returned ForwardingInfo is to be used by each
// hop to authenticate the information given to it by the prior hop.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ForwardingInstructions() ForwardingInfo {
	fwdInst := r.processedPacket.ForwardingInstructions

	var nextHop lnwire.ShortChannelID
	switch r.processedPacket.Action {
	case sphinx.ExitNode:
		nextHop = exitHop
	case sphinx.MoreHops:
		s := binary.BigEndian.Uint64(fwdInst.NextAddress[:])
		nextHop = lnwire.NewShortChanIDFromInt(s)
	}

	return ForwardingInfo{
		Network:         BitcoinHop,
		NextHop:         nextHop,
		AmountToForward: lnwire.MilliSatoshi(fwdInst.ForwardAmount),
		OutgoingCTLV:    fwdInst.OutgoingCltv,
	}
}

// ExtractErrorEncrypter decodes and returns the ErrorEncrypter for this hop,
// along with a failure code to signal if the decoding was successful. The
// ErrorEncrypter is used to encrypt errors back to the sender in the event that
// a payment fails.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ExtractErrorEncrypter(
	extracter ErrorEncrypterExtracter) (ErrorEncrypter, lnwire.FailCode) {

	return extracter(r.ogPacket.EphemeralKey)
}

// OnionProcessor is responsible for keeping all sphinx dependent parts inside
// and expose only decoding function. With such approach we give freedom for
// subsystems which wants to decode sphinx path to not be dependable from
// sphinx at all.
//
// NOTE: The reason for keeping decoder separated from hop iterator is too
// maintain the hop iterator abstraction. Without it the structures which using
// the hop iterator should contain sphinx router which makes their creations in
// tests dependent from the sphinx internal parts.
type OnionProcessor struct {
	router *sphinx.Router
}

// NewOnionProcessor creates new instance of decoder.
func NewOnionProcessor(router *sphinx.Router) *OnionProcessor {
	return &OnionProcessor{router}
}

// Start spins up the onion processor's sphinx router.
func (p *OnionProcessor) Start() error {
	return p.router.Start()
}

// Stop shutsdown the onion processor's sphinx router.
func (p *OnionProcessor) Stop() error {
	p.router.Stop()
	return nil
}

// DecodeHopIterator attempts to decode a valid sphinx packet from the passed io.Reader
// instance using the rHash as the associated data when checking the relevant
// MACs during the decoding process.
func (p *OnionProcessor) DecodeHopIterator(r io.Reader, rHash []byte,
	incomingCltv uint32) (HopIterator, lnwire.FailCode) {

	onionPkt := &sphinx.OnionPacket{}
	if err := onionPkt.Decode(r); err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			log.Errorf("unable to decode onion packet: %v", err)
			return nil, lnwire.CodeInvalidOnionKey
		}
	}

	// Attempt to process the Sphinx packet. We include the payment hash of
	// the HTLC as it's authenticated within the Sphinx packet itself as
	// associated data in order to thwart attempts a replay attacks. In the
	// case of a replay, an attacker is *forced* to use the same payment
	// hash twice, thereby losing their money entirely.
	sphinxPacket, err := p.router.ProcessOnionPacket(
		onionPkt, rHash, incomingCltv,
	)
	if err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionHMAC:
			return nil, lnwire.CodeInvalidOnionHmac
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			log.Errorf("unable to process onion packet: %v", err)
			return nil, lnwire.CodeInvalidOnionKey
		}
	}

	return makeSphinxHopIterator(onionPkt, sphinxPacket), lnwire.CodeNone
}

// DecodeHopIteratorRequest encapsulates all date necessary to process an onion
// packet, perform sphinx replay detection, and schedule the entry for garbage
// collection.
type DecodeHopIteratorRequest struct {
	OnionReader  io.Reader
	RHash        []byte
	IncomingCltv uint32
}

// DecodeHopIteratorResponse encapsulates the outcome of a batched sphinx onion
// processing.
type DecodeHopIteratorResponse struct {
	HopIterator HopIterator
	FailCode    lnwire.FailCode
}

// Result returns the (HopIterator, lnwire.FailCode) tuple, which should
// correspond to the index of a particular DecodeHopIteratorRequest.
//
// NOTE: The HopIterator should be considered invalid if the fail code is
// anything but lnwire.CodeNone.
func (r *DecodeHopIteratorResponse) Result() (HopIterator, lnwire.FailCode) {
	return r.HopIterator, r.FailCode
}

// DecodeHopIterators performs batched decoding and validation of incoming
// sphinx packets. For the same `id`, this method will return the same iterators
// and failcodes upon subsequent invocations.
//
// NOTE: In order for the responses to be valid, the caller must guarantee that
// the presented readers and rhashes *NEVER* deviate across invocations for the
// same id.
func (p *OnionProcessor) DecodeHopIterators(id []byte,
	reqs []DecodeHopIteratorRequest) ([]DecodeHopIteratorResponse, error) {

	var (
		batchSize = len(reqs)
		onionPkts = make([]sphinx.OnionPacket, batchSize)
		resps     = make([]DecodeHopIteratorResponse, batchSize)
	)

	tx := p.router.BeginTxn(id, batchSize)

	for i, req := range reqs {
		onionPkt := &onionPkts[i]
		resp := &resps[i]

		err := onionPkt.Decode(req.OnionReader)
		switch err {
		case nil:
			// success

		case sphinx.ErrInvalidOnionVersion:
			resp.FailCode = lnwire.CodeInvalidOnionVersion
			continue

		case sphinx.ErrInvalidOnionKey:
			resp.FailCode = lnwire.CodeInvalidOnionKey
			continue

		default:
			log.Errorf("unable to decode onion packet: %v", err)
			resp.FailCode = lnwire.CodeInvalidOnionKey
			continue
		}

		err = tx.ProcessOnionPacket(
			uint16(i), onionPkt, req.RHash, req.IncomingCltv,
		)
		switch err {
		case nil:
			// success

		case sphinx.ErrInvalidOnionVersion:
			resp.FailCode = lnwire.CodeInvalidOnionVersion
			continue

		case sphinx.ErrInvalidOnionHMAC:
			resp.FailCode = lnwire.CodeInvalidOnionHmac
			continue

		case sphinx.ErrInvalidOnionKey:
			resp.FailCode = lnwire.CodeInvalidOnionKey
			continue

		default:
			log.Errorf("unable to process onion packet: %v", err)
			resp.FailCode = lnwire.CodeInvalidOnionKey
			continue
		}
	}

	// With that batch created, we will now attempt to write the shared
	// secrets to disk. This operation will returns the set of indices that
	// were detected as replays, and the computed sphinx packets for all
	// indices that did not fail the above loop. Only indices that are not
	// in the replay set should be considered valid, as they are
	// opportunistically computed.
	packets, replays, err := tx.Commit()
	if err != nil {
		log.Errorf("unable to process onion packet batch %x: %v",
			id, err)

		// If we failed to commit the batch to the secret share log, we
		// will mark all not-yet-failed channels with a temporary
		// channel failure and exit since we cannot proceed.
		for i := range resps {
			resp := &resps[i]

			// Skip any indexes that already failed onion decoding.
			if resp.FailCode != lnwire.CodeNone {
				continue
			}

			log.Errorf("unable to process onion packet %x-%v",
				id, i)
			resp.FailCode = lnwire.CodeTemporaryChannelFailure
		}

		// TODO(conner): return real errors to caller so link can fail?
		return resps, err
	}

	// Otherwise, the commit was successful. Now we will post process any
	// remaining packets, additionally failing any that were included in the
	// replay set.
	for i := range resps {
		resp := &resps[i]

		// Skip any indexes that already failed onion decoding.
		if resp.FailCode != lnwire.CodeNone {
			continue
		}

		// If this index is contained in the replay set, mark it with a
		// temporary channel failure error code. We infer that the
		// offending error was due to a replayed packet because this
		// index was found in the replay set.
		if replays.Contains(uint16(i)) {
			log.Errorf("unable to process onion packet: %v",
				sphinx.ErrReplayedPacket)
			resp.FailCode = lnwire.CodeTemporaryChannelFailure
			continue
		}

		// Finally, construct a hop iterator from our processed sphinx
		// packet, simultaneously caching the original onion packet.
		resp.HopIterator = makeSphinxHopIterator(&onionPkts[i], &packets[i])
	}

	return resps, nil
}

// ExtractErrorEncrypter takes an io.Reader which should contain the onion
// packet as original received by a forwarding node and creates an
// ErrorEncrypter instance using the derived shared secret. In the case that en
// error occurs, a lnwire failure code detailing the parsing failure will be
// returned.
func (p *OnionProcessor) ExtractErrorEncrypter(ephemeralKey *btcec.PublicKey) (
	ErrorEncrypter, lnwire.FailCode) {

	onionObfuscator, err := sphinx.NewOnionErrorEncrypter(
		p.router, ephemeralKey,
	)
	if err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionHMAC:
			return nil, lnwire.CodeInvalidOnionHmac
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			log.Errorf("unable to process onion packet: %v", err)
			return nil, lnwire.CodeInvalidOnionKey
		}
	}

	return &SphinxErrorEncrypter{
		OnionErrorEncrypter: onionObfuscator,
		EphemeralKey:        ephemeralKey,
	}, lnwire.CodeNone
}
<|endoftext|>


<commit_before>package printer

import (
	"fmt"
	"io"
	"strings"
)

//
// RustPrinter implement the Printer interface for Rust programs
//
type RustPrinter struct {
	Printer

	level    int
	sameline bool
	w        io.Writer
}

func (p *RustPrinter) Reset() {
	p.level = 0
	p.sameline = false
}

func (p *RustPrinter) PushContext() {
}

func (p *RustPrinter) PopContext() {
}

func (p *RustPrinter) SetWriter(w io.Writer) {
	p.w = w
}

func (p *RustPrinter) UpdateLevel(delta int) {
	p.level += delta
}

func (p *RustPrinter) SameLine() {
	p.sameline = true
}

func (p *RustPrinter) IsSameLine() bool {
	return p.sameline
}

func (p *RustPrinter) Chop(line string) string {
	return strings.TrimRight(line, COMMANL)
}

func (p *RustPrinter) indent() string {
	if p.sameline {
		p.sameline = false
		return ""
	}

	return strings.Repeat("  ", p.level)
}

func (p *RustPrinter) Print(values ...string) {
	fmt.Fprint(p.w, strings.Join(values, " "))
}

func (p *RustPrinter) PrintLevel(term string, values ...string) {
	fmt.Fprint(p.w, p.indent(), strings.Join(values, " "), term)
}

func (p *RustPrinter) PrintfLevel(term string, format string, values ...interface{}) {
	fmt.Fprintf(p.w, p.indent()+format+term, values...)
}

func (p *RustPrinter) PrintBlockStart(b BlockType) {
	var open string

	switch b {
	case CONST, VAR:
		open = "("
	default:
		open = "{"
	}

	p.PrintLevel(NL, open)
	p.UpdateLevel(UP)
}

func (p *RustPrinter) PrintBlockEnd(b BlockType) {
	var close string

	switch b {
	case CONST, VAR:
		close = ")"
	default:
		close = "}"
	}

	p.UpdateLevel(DOWN)
	p.PrintLevel(NONE, close)
}

func (p *RustPrinter) PrintPackage(name string) {
	p.PrintLevel(NL, "package", name)
}

func (p *RustPrinter) PrintImport(name, path string) {
	p.PrintLevel(NL, "import", name, path)
}

func (p *RustPrinter) PrintType(name, typedef string) {
	if strings.Contains(typedef, "%") {
		p.PrintfLevel(NL, typedef, name)
	} else {
		p.PrintLevel(NL, "type", name, typedef)
	}
}

func (p *RustPrinter) PrintValue(vtype, typedef, names, values string, ntuple, vtuple bool) {
	if vtype == "var" {
		if len(values) > 0 {
			vtype = "let"
		} else {
			vtype = ""
		}
	} else if vtype == "const" {
		vtype = "static"
	}
	p.PrintLevel(NONE, vtype, names)
	if len(typedef) > 0 {
		p.Print(" ", typedef)
	}
	if len(values) > 0 {
		p.Print(" =", values)
	}
	p.Print("\n")
}

func (p *RustPrinter) PrintStmt(stmt, expr string) {
	if len(stmt) > 0 {
		p.PrintLevel(SEMI, stmt, expr)
	} else {
		p.PrintLevel(SEMI, expr)
	}
}

func (p *RustPrinter) PrintReturn(expr string, tuple bool) {
	p.PrintStmt("return", expr)
}

func (p *RustPrinter) PrintFunc(receiver, name, params, results string) {
	p.PrintfLevel(NONE, "%sfn ", IfTrue("pub ", IsPublic(name)))
	if len(receiver) > 0 {
		fmt.Fprintf(p.w, "(%s) ", receiver)
	}
	fmt.Fprintf(p.w, "%s(%s) ", name, params)
	if len(results) > 0 {
		p.Print("->", "")

		if strings.ContainsAny(results, " ,") {
			// name type or multiple types
			fmt.Fprintf(p.w, "(%s) ", results)
		} else {
			p.Print(results, "")
		}
	}
}

func (p *RustPrinter) PrintFor(init, cond, post string) {
	p.PrintLevel(NONE, "for ")
	if len(init) > 0 {
		p.Print(init)
	}
	if len(init) > 0 || len(post) > 0 {
		p.Print("; ")
	}

	p.Print(cond)

	if len(post) > 0 {
		p.Print(";", post)
	}

	p.Print("")
}

func (p *RustPrinter) PrintRange(key, value, expr string) {
	p.PrintLevel(NONE, "for", key)

	if len(value) > 0 {
		p.Print(",", value)
	}

	p.Print(" := range", expr)

}

func (p *RustPrinter) PrintSwitch(init, expr string) {
	p.PrintLevel(NONE, "switch ")
	if len(init) > 0 {
		p.Print(init + "; ")
	}
	p.Print(expr)
}

func (p *RustPrinter) PrintCase(expr string) {
	if len(expr) > 0 {
		p.PrintLevel(SEMI, "case", expr)
	} else {
		p.PrintLevel(NL, "default:")
	}
}

func (p *RustPrinter) PrintEndCase() {
	// nothing to do
}

func (p *RustPrinter) PrintIf(init, cond string) {
	p.PrintLevel(NONE, "if ")
	if len(init) > 0 {
		p.Print(init + "; ")
	}
	p.Print(cond, "")
}

func (p *RustPrinter) PrintElse() {
	p.Print(" else ")
}

func (p *RustPrinter) PrintEmpty() {
	p.PrintLevel(SEMI, "")
}

func (p *RustPrinter) PrintAssignment(lhs, op, rhs string, ltuple, rtuple bool) {
	if op == ":=" {
		lhs = "let " + lhs
		op = "="
	}

	p.PrintLevel(NL, lhs, op, rhs)
}

func (p *RustPrinter) PrintSend(ch, value string) {
	p.PrintLevel(SEMI, ch, "<-", value)
}

func (p *RustPrinter) FormatIdent(id string) string {
	return id
}

func (p *RustPrinter) FormatLiteral(lit string) string {
	return lit
}

func (p *RustPrinter) FormatCompositeLit(typedef, elt string) string {
	return fmt.Sprintf("%s{%s}", typedef, elt)
}

func (p *RustPrinter) FormatEllipsis(expr string) string {
	return fmt.Sprintf("...%s", expr)
}

func (p *RustPrinter) FormatStar(expr string) string {
	return fmt.Sprintf("*%s", expr)
}

func (p *RustPrinter) FormatParen(expr string) string {
	return fmt.Sprintf("(%s)", expr)
}

func (p *RustPrinter) FormatUnary(op, operand string) string {
	return fmt.Sprintf("%s%s", op, operand)
}

func (p *RustPrinter) FormatBinary(lhs, op, rhs string) string {
	return fmt.Sprintf("%s %s %s", lhs, op, rhs)
}

func (p *RustPrinter) FormatPair(v Pair, t FieldType) string {
	switch t {
	case METHOD:
		return p.indent() + v.Name() + v.Value() + NL
	case FIELD:
		return fmt.Sprintf("%s%s: %s%s", p.indent(), v.Name(), v.Value(), COMMANL)
	case PARAM:
		return fmt.Sprintf("%s: %s%s", v.Name(), v.Value(), COMMA)
	default:
		return v.String() + COMMA
	}
}

func (p *RustPrinter) FormatArray(len, elt string) string {
	return fmt.Sprintf("[%s]%s", len, elt)
}

func (p *RustPrinter) FormatArrayIndex(array, index string) string {
	return fmt.Sprintf("%s[%s]", array, index)
}

func (p *RustPrinter) FormatSlice(slice, low, high, max string) string {
	if max == "" {
		return fmt.Sprintf("%s[%s:%s]", slice, low, high)
	} else {
		return fmt.Sprintf("%s[%s:%s:%s]", slice, low, high, max)
	}
}

func (p *RustPrinter) FormatMap(key, elt string) string {
	return fmt.Sprintf("[%s]%s", key, elt)
}

func (p *RustPrinter) FormatKeyValue(key, value string) string {
	return fmt.Sprintf("%s: %s", key, value)
}

func (p *RustPrinter) FormatStruct(fields string) string {
	if len(fields) > 0 {
		return fmt.Sprintf("struct %%s {\n%s\n}", p.Chop(fields))
	} else {
		return "struct %%s;"
	}
}

func (p *RustPrinter) FormatInterface(methods string) string {
	if len(methods) > 0 {
		return fmt.Sprintf("interface{\n%s}", methods)
	} else {
		return "interface{}"
	}
}

func (p *RustPrinter) FormatChan(chdir, mtype string) string {
	return fmt.Sprintf("%s %s", chdir, mtype)
}

func (p *RustPrinter) FormatCall(fun, args string, isFuncLit bool) string {
	return fmt.Sprintf("%s(%s)", fun, args)
}

func (p *RustPrinter) FormatFuncType(params, results string, withFunc bool) string {
	prefix := ""
	if withFunc {
		prefix = "fn"
	}

	if len(results) == 0 {
		// no results
		return fmt.Sprintf("%s(%s)", prefix, params)
	}

	if strings.ContainsAny(results, ", ") {
		// name type or multiple types
		return fmt.Sprintf("%s(%s) (%s)", prefix, params, results)
	}

	// just type
	return fmt.Sprintf("%s(%s) %s", prefix, params, results)
}

func (p *RustPrinter) FormatFuncLit(ftype, body string) string {
	return fmt.Sprintf("fn%s %s", ftype, body)
}

func (p *RustPrinter) FormatSelector(pname, sel string, isObject bool) string {
	return fmt.Sprintf("%s.%s", pname, sel)
}

func (p *RustPrinter) FormatTypeAssert(orig, assert string) string {
	return fmt.Sprintf("%s.(%s)", orig, assert)
}
<commit_msg>fixed "chop" of last comma in parameter list and return of a tuple.<commit_after>package printer

import (
	"fmt"
	"io"
	"strings"
)

//
// RustPrinter implement the Printer interface for Rust programs
//
type RustPrinter struct {
	Printer

	level    int
	sameline bool
	w        io.Writer
}

func (p *RustPrinter) Reset() {
	p.level = 0
	p.sameline = false
}

func (p *RustPrinter) PushContext() {
}

func (p *RustPrinter) PopContext() {
}

func (p *RustPrinter) SetWriter(w io.Writer) {
	p.w = w
}

func (p *RustPrinter) UpdateLevel(delta int) {
	p.level += delta
}

func (p *RustPrinter) SameLine() {
	p.sameline = true
}

func (p *RustPrinter) IsSameLine() bool {
	return p.sameline
}

func (p *RustPrinter) Chop(line string) string {
	return strings.TrimRight(line, ", \n")
}

func (p *RustPrinter) indent() string {
	if p.sameline {
		p.sameline = false
		return ""
	}

	return strings.Repeat("  ", p.level)
}

func (p *RustPrinter) Print(values ...string) {
	fmt.Fprint(p.w, strings.Join(values, " "))
}

func (p *RustPrinter) PrintLevel(term string, values ...string) {
	fmt.Fprint(p.w, p.indent(), strings.Join(values, " "), term)
}

func (p *RustPrinter) PrintfLevel(term string, format string, values ...interface{}) {
	fmt.Fprintf(p.w, p.indent()+format+term, values...)
}

func (p *RustPrinter) PrintBlockStart(b BlockType) {
	var open string

	switch b {
	case CONST, VAR:
		open = "("
	default:
		open = "{"
	}

	p.PrintLevel(NL, open)
	p.UpdateLevel(UP)
}

func (p *RustPrinter) PrintBlockEnd(b BlockType) {
	var close string

	switch b {
	case CONST, VAR:
		close = ")"
	default:
		close = "}"
	}

	p.UpdateLevel(DOWN)
	p.PrintLevel(NONE, close)
}

func (p *RustPrinter) PrintPackage(name string) {
	p.PrintLevel(NL, "package", name)
}

func (p *RustPrinter) PrintImport(name, path string) {
	p.PrintLevel(NL, "import", name, path)
}

func (p *RustPrinter) PrintType(name, typedef string) {
	if strings.Contains(typedef, "%") {
		p.PrintfLevel(NL, typedef, name)
	} else {
		p.PrintLevel(NL, "type", name, typedef)
	}
}

func (p *RustPrinter) PrintValue(vtype, typedef, names, values string, ntuple, vtuple bool) {
	if vtype == "var" {
		if len(values) > 0 {
			vtype = "let"
		} else {
			vtype = ""
		}
	} else if vtype == "const" {
		vtype = "static"
	}
	p.PrintLevel(NONE, vtype, names)
	if len(typedef) > 0 {
		p.Print(" ", typedef)
	}
	if len(values) > 0 {
		p.Print(" =", values)
	}
	p.Print("\n")
}

func (p *RustPrinter) PrintStmt(stmt, expr string) {
	if len(stmt) > 0 {
		p.PrintLevel(SEMI, stmt, expr)
	} else {
		p.PrintLevel(SEMI, expr)
	}
}

func (p *RustPrinter) PrintReturn(expr string, tuple bool) {
	if tuple {
		expr = "(" + expr + ")"
	}

	p.PrintStmt("return", expr)
}

func (p *RustPrinter) PrintFunc(receiver, name, params, results string) {
	p.PrintfLevel(NONE, "%sfn ", IfTrue("pub ", IsPublic(name)))
	if len(receiver) > 0 {
		fmt.Fprintf(p.w, "(%s) ", receiver)
	}
	fmt.Fprintf(p.w, "%s(%s) ", name, params)
	if len(results) > 0 {
		p.Print("->", "")

		if strings.ContainsAny(results, " ,") {
			// name type or multiple types
			fmt.Fprintf(p.w, "(%s) ", results)
		} else {
			p.Print(results, "")
		}
	}
}

func (p *RustPrinter) PrintFor(init, cond, post string) {
	p.PrintLevel(NONE, "for ")
	if len(init) > 0 {
		p.Print(init)
	}
	if len(init) > 0 || len(post) > 0 {
		p.Print("; ")
	}

	p.Print(cond)

	if len(post) > 0 {
		p.Print(";", post)
	}

	p.Print("")
}

func (p *RustPrinter) PrintRange(key, value, expr string) {
	p.PrintLevel(NONE, "for", key)

	if len(value) > 0 {
		p.Print(",", value)
	}

	p.Print(" := range", expr)

}

func (p *RustPrinter) PrintSwitch(init, expr string) {
	p.PrintLevel(NONE, "switch ")
	if len(init) > 0 {
		p.Print(init + "; ")
	}
	p.Print(expr)
}

func (p *RustPrinter) PrintCase(expr string) {
	if len(expr) > 0 {
		p.PrintLevel(SEMI, "case", expr)
	} else {
		p.PrintLevel(NL, "default:")
	}
}

func (p *RustPrinter) PrintEndCase() {
	// nothing to do
}

func (p *RustPrinter) PrintIf(init, cond string) {
	p.PrintLevel(NONE, "if ")
	if len(init) > 0 {
		p.Print(init + "; ")
	}
	p.Print(cond, "")
}

func (p *RustPrinter) PrintElse() {
	p.Print(" else ")
}

func (p *RustPrinter) PrintEmpty() {
	p.PrintLevel(SEMI, "")
}

func (p *RustPrinter) PrintAssignment(lhs, op, rhs string, ltuple, rtuple bool) {
	if op == ":=" {
		lhs = "let " + lhs
		op = "="
	}

	p.PrintLevel(NL, lhs, op, rhs)
}

func (p *RustPrinter) PrintSend(ch, value string) {
	p.PrintLevel(SEMI, ch, "<-", value)
}

func (p *RustPrinter) FormatIdent(id string) string {
	return id
}

func (p *RustPrinter) FormatLiteral(lit string) string {
	return lit
}

func (p *RustPrinter) FormatCompositeLit(typedef, elt string) string {
	return fmt.Sprintf("%s{%s}", typedef, elt)
}

func (p *RustPrinter) FormatEllipsis(expr string) string {
	return fmt.Sprintf("...%s", expr)
}

func (p *RustPrinter) FormatStar(expr string) string {
	return fmt.Sprintf("*%s", expr)
}

func (p *RustPrinter) FormatParen(expr string) string {
	return fmt.Sprintf("(%s)", expr)
}

func (p *RustPrinter) FormatUnary(op, operand string) string {
	return fmt.Sprintf("%s%s", op, operand)
}

func (p *RustPrinter) FormatBinary(lhs, op, rhs string) string {
	return fmt.Sprintf("%s %s %s", lhs, op, rhs)
}

func (p *RustPrinter) FormatPair(v Pair, t FieldType) string {
	switch t {
	case METHOD:
		return p.indent() + v.Name() + v.Value() + NL
	case FIELD:
		return fmt.Sprintf("%s%s: %s%s", p.indent(), v.Name(), v.Value(), COMMANL)
	case PARAM:
		return fmt.Sprintf("%s: %s%s", v.Name(), v.Value(), COMMA)
	default:
		return v.String() + COMMA
	}
}

func (p *RustPrinter) FormatArray(len, elt string) string {
	return fmt.Sprintf("[%s]%s", len, elt)
}

func (p *RustPrinter) FormatArrayIndex(array, index string) string {
	return fmt.Sprintf("%s[%s]", array, index)
}

func (p *RustPrinter) FormatSlice(slice, low, high, max string) string {
	if max == "" {
		return fmt.Sprintf("%s[%s:%s]", slice, low, high)
	} else {
		return fmt.Sprintf("%s[%s:%s:%s]", slice, low, high, max)
	}
}

func (p *RustPrinter) FormatMap(key, elt string) string {
	return fmt.Sprintf("[%s]%s", key, elt)
}

func (p *RustPrinter) FormatKeyValue(key, value string) string {
	return fmt.Sprintf("%s: %s", key, value)
}

func (p *RustPrinter) FormatStruct(fields string) string {
	if len(fields) > 0 {
		return fmt.Sprintf("struct %%s {\n%s\n}", p.Chop(fields))
	} else {
		return "struct %%s;"
	}
}

func (p *RustPrinter) FormatInterface(methods string) string {
	if len(methods) > 0 {
		return fmt.Sprintf("interface{\n%s}", methods)
	} else {
		return "interface{}"
	}
}

func (p *RustPrinter) FormatChan(chdir, mtype string) string {
	return fmt.Sprintf("%s %s", chdir, mtype)
}

func (p *RustPrinter) FormatCall(fun, args string, isFuncLit bool) string {
	return fmt.Sprintf("%s(%s)", fun, args)
}

func (p *RustPrinter) FormatFuncType(params, results string, withFunc bool) string {
	prefix := ""
	if withFunc {
		prefix = "fn"
	}

	if len(results) == 0 {
		// no results
		return fmt.Sprintf("%s(%s)", prefix, params)
	}

	if strings.ContainsAny(results, ", ") {
		// name type or multiple types
		return fmt.Sprintf("%s(%s) (%s)", prefix, params, results)
	}

	// just type
	return fmt.Sprintf("%s(%s) %s", prefix, params, results)
}

func (p *RustPrinter) FormatFuncLit(ftype, body string) string {
	return fmt.Sprintf("fn%s %s", ftype, body)
}

func (p *RustPrinter) FormatSelector(pname, sel string, isObject bool) string {
	return fmt.Sprintf("%s.%s", pname, sel)
}

func (p *RustPrinter) FormatTypeAssert(orig, assert string) string {
	return fmt.Sprintf("%s.(%s)", orig, assert)
}
<|endoftext|>


<commit_before>package flipdots

import (
	"image"
	"image/draw"
	"io/ioutil"
	"math"
	"net"

	"github.com/golang/freetype"
	colorful "github.com/lucasb-eyer/go-colorful"
	"golang.org/x/image/font"
)

type FlipDots struct {
	Addr        *net.UDPAddr
	Connection  *net.UDPConn
	Width       int
	Height      int
	Dpi         float64
	FontSize    float64
	FontSpacing float64
	Foreground  *image.Uniform
	Background  *image.Uniform
}

func listToByte(s string) byte {
	b := byte(0)
	for i := 0; i < 8; i++ {
		if s[i] == '1' {
			b += byte(math.Pow(float64(2), float64(7-i)))
		}
	}

	return b
}

func matrixToPacket(s string) []byte {
	b := []byte{}

	for i := 0; i < len(s)/8; i++ {
		b = append(b, listToByte(s[i*8:i*8+8]))
	}

	return b
}

func (fd *FlipDots) ImageToMatrix(img image.Image) string {
	imgmap := ""
	for row := 0; row < fd.Height; row++ {
		for column := 0; column < fd.Width; column++ {
			color := img.At(column, row)
			cf, _ := colorful.MakeColor(color)
			l, _, _ := cf.Lab()
			if l >= 0.5 {
				imgmap = imgmap + "1"
			} else {
				imgmap = imgmap + "0"
			}
		}
	}

	return imgmap
}

func (fd *FlipDots) TextToImage(text, ttfPath string) (image.Image, error) {
	rgba := image.NewRGBA(image.Rect(0, 0, fd.Width*16, fd.Height))

	fontBytes, err := ioutil.ReadFile(ttfPath)
	if err != nil {
		return rgba, err
	}
	ttf, err := freetype.ParseFont(fontBytes)
	if err != nil {
		return rgba, err
	}

	draw.Draw(rgba, rgba.Bounds(), fd.Background, image.ZP, draw.Src)
	c := freetype.NewContext()
	c.SetDPI(fd.Dpi)
	c.SetFont(ttf)
	c.SetFontSize(fd.FontSize)
	c.SetClip(rgba.Bounds())
	c.SetDst(rgba)
	c.SetSrc(fd.Foreground)

	// c.SetHinting(font.HintingNone)
	c.SetHinting(font.HintingFull)

	fh := c.PointToFixed(fd.FontSize/2.0) / 64
	yc := 1 + (float64(fd.Height) / 2.0) + (float64(fh) / 2.0)

	pt := freetype.Pt(1, int(yc))
	s, err := c.DrawString(text, pt)
	if err != nil {
		return rgba, err
	}
	// pt.Y += c.PointToFix32(fd.FontSize * fd.FontSpacing)

	return rgba.SubImage(image.Rect(0, 0, int(float64(s.X)/64), fd.Height)), nil
}

func (fd *FlipDots) Clear() error {
	i := image.NewRGBA(image.Rect(0, 0, fd.Width, fd.Height))
	draw.Draw(i, i.Bounds(), fd.Background, image.ZP, draw.Src)
	return fd.SendImage(i)
}

func (fd *FlipDots) SendImage(img image.Image) error {
	imgmap := fd.ImageToMatrix(img)
	_, err := fd.Connection.Write(matrixToPacket(imgmap))
	return err
}

func Init(addr string, width int, height int) (FlipDots, error) {
	fd := FlipDots{
		Width:       width,
		Height:      height,
		Dpi:         72.0,
		FontSize:    12.0,
		FontSpacing: 1.1,
		Foreground:  image.Black,
		Background:  image.White,
	}

	serverAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return fd, err
	}
	fd.Addr = serverAddr

	conn, err := net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		return fd, err
	}
	fd.Connection = conn
	return fd, nil
}
<commit_msg>Added ScrollImage method<commit_after>package flipdots

import (
	"image"
	"image/draw"
	"io/ioutil"
	"math"
	"net"
	"time"

	"github.com/golang/freetype"
	colorful "github.com/lucasb-eyer/go-colorful"
	"golang.org/x/image/font"
)

type FlipDots struct {
	Addr        *net.UDPAddr
	Connection  *net.UDPConn
	Width       int
	Height      int
	Dpi         float64
	FontSize    float64
	FontSpacing float64
	Foreground  *image.Uniform
	Background  *image.Uniform
}

func listToByte(s string) byte {
	b := byte(0)
	for i := 0; i < 8; i++ {
		if s[i] == '1' {
			b += byte(math.Pow(float64(2), float64(7-i)))
		}
	}

	return b
}

func matrixToPacket(s string) []byte {
	b := []byte{}

	for i := 0; i < len(s)/8; i++ {
		b = append(b, listToByte(s[i*8:i*8+8]))
	}

	return b
}

func (fd *FlipDots) ImageToMatrix(img image.Image) string {
	imgmap := ""
	for row := 0; row < fd.Height; row++ {
		for column := 0; column < fd.Width; column++ {
			color := img.At(column, row)
			cf, _ := colorful.MakeColor(color)
			l, _, _ := cf.Lab()
			if l >= 0.5 {
				imgmap = imgmap + "1"
			} else {
				imgmap = imgmap + "0"
			}
		}
	}

	return imgmap
}

func (fd *FlipDots) ScrollImage(img image.Image) error {
	b := img.Bounds()
	for x := 0; x < b.Max.X; x++ {
		subimg := image.NewRGBA(image.Rect(0, 0, fd.Width*16, fd.Height))
		draw.Draw(subimg, b, fd.Background, image.ZP, draw.Src)
		draw.Draw(subimg, b, img, b.Min.Add(image.Pt(x, 0)), draw.Src)

		err := fd.SendImage(subimg)
		if err != nil {
			return err
		}

		time.Sleep(55 * time.Millisecond)
	}

	return nil
}

func (fd *FlipDots) TextToImage(text, ttfPath string) (image.Image, error) {
	rgba := image.NewRGBA(image.Rect(0, 0, fd.Width*16, fd.Height))

	fontBytes, err := ioutil.ReadFile(ttfPath)
	if err != nil {
		return rgba, err
	}
	ttf, err := freetype.ParseFont(fontBytes)
	if err != nil {
		return rgba, err
	}

	draw.Draw(rgba, rgba.Bounds(), fd.Background, image.ZP, draw.Src)
	c := freetype.NewContext()
	c.SetDPI(fd.Dpi)
	c.SetFont(ttf)
	c.SetFontSize(fd.FontSize)
	c.SetClip(rgba.Bounds())
	c.SetDst(rgba)
	c.SetSrc(fd.Foreground)

	// c.SetHinting(font.HintingNone)
	c.SetHinting(font.HintingFull)

	fh := c.PointToFixed(fd.FontSize/2.0) / 64
	yc := 1 + (float64(fd.Height) / 2.0) + (float64(fh) / 2.0)

	pt := freetype.Pt(1, int(yc))
	s, err := c.DrawString(text, pt)
	if err != nil {
		return rgba, err
	}
	// pt.Y += c.PointToFix32(fd.FontSize * fd.FontSpacing)

	return rgba.SubImage(image.Rect(0, 0, int(float64(s.X)/64), fd.Height)), nil
}

func (fd *FlipDots) Clear() error {
	i := image.NewRGBA(image.Rect(0, 0, fd.Width, fd.Height))
	draw.Draw(i, i.Bounds(), fd.Background, image.ZP, draw.Src)
	return fd.SendImage(i)
}

func (fd *FlipDots) SendImage(img image.Image) error {
	imgmap := fd.ImageToMatrix(img)
	_, err := fd.Connection.Write(matrixToPacket(imgmap))
	return err
}

func Init(addr string, width int, height int) (FlipDots, error) {
	fd := FlipDots{
		Width:       width,
		Height:      height,
		Dpi:         72.0,
		FontSize:    12.0,
		FontSpacing: 1.1,
		Foreground:  image.Black,
		Background:  image.White,
	}

	serverAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return fd, err
	}
	fd.Addr = serverAddr

	conn, err := net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		return fd, err
	}
	fd.Connection = conn
	return fd, nil
}
<|endoftext|>


<commit_before>package delta_test

import (
	"testing"

	"github.com/GeoNet/delta/meta"
)

func TestReceivers(t *testing.T) {

	var receivers meta.DeployedReceiverList
	loadListFile(t, "../install/receivers.csv", &receivers)

	t.Run("check for particular receiver installation overlaps", func(t *testing.T) {
		installs := make(map[string]meta.DeployedReceiverList)
		for _, s := range receivers {
			if _, ok := installs[s.Model]; !ok {
				installs[s.Model] = meta.DeployedReceiverList{}
			}
			installs[s.Model] = append(installs[s.Model], s)
		}

		for _, v := range installs {
			for i := 0; i < len(v); i++ {
				for j := i + 1; j < len(v); j++ {
					if v[i].Serial != v[j].Serial {
						continue
					}
					if v[i].End.Before(v[j].Start) {
						continue
					}
					if v[i].Start.After(v[j].End) {
						continue
					}
					if v[i].End.Equal(v[j].Start) {
						continue
					}
					if v[i].Start.Equal(v[j].End) {
						continue
					}

					t.Errorf("receiver %s [%s] at %s has overlap with %s between times %s and %s",
						v[i].Model, v[i].Serial, v[i].Mark, v[j].Mark, v[i].Start.Format(meta.DateTimeFormat), v[i].End.Format(meta.DateTimeFormat))
				}
			}
		}
	})

	t.Run("check for receiver installation equipment overlaps", func(t *testing.T) {
		installs := make(map[string]meta.DeployedReceiverList)
		for _, s := range receivers {
			if _, ok := installs[s.Mark]; !ok {
				installs[s.Mark] = meta.DeployedReceiverList{}
			}
			installs[s.Mark] = append(installs[s.Model], s)
		}

		for _, v := range installs {
			for i := 0; i < len(v); i++ {
				for j := i + 1; j < len(v); j++ {
					if v[i].End.Before(v[j].Start) {
						continue
					}
					if v[i].Start.After(v[j].End) {
						continue
					}
					if v[i].End.Equal(v[j].Start) {
						continue
					}
					if v[i].Start.Equal(v[j].End) {
						continue
					}

					t.Errorf("receivers %s [%s] / %s [%s] at %s has overlap between %s and %s",
						v[i].Model, v[i].Serial, v[j].Model, v[j].Serial, v[i].Mark, v[i].Start.Format(meta.DateTimeFormat), v[i].End.Format(meta.DateTimeFormat))
				}
			}
		}
	})

	t.Run("check for missing receiver marks", func(t *testing.T) {
		var marks meta.MarkList
		loadListFile(t, "../network/marks.csv", &marks)

		keys := make(map[string]interface{})
		for _, m := range marks {
			keys[m.Code] = true
		}

		for _, r := range receivers {
			if _, ok := keys[r.Mark]; ok {
				continue
			}
			t.Errorf("unable to find receiver mark %-5s", r.Mark)
		}
	})

	t.Run("check for missing receiver assets", func(t *testing.T) {
		var assets meta.AssetList
		loadListFile(t, "../assets/receivers.csv", &assets)
		for _, r := range receivers {
			var found bool
			for _, a := range assets {
				if a.Model != r.Model {
					continue
				}
				if a.Serial != r.Serial {
					continue
				}
				found = true
			}
			if found {
				continue
			}
			t.Errorf("unable to find receiver asset: %s [%s]", r.Model, r.Serial)
		}
	})

}
<commit_msg>fixed receiver overlap test bug (#310)<commit_after>package delta_test

import (
	"testing"

	"github.com/GeoNet/delta/meta"
)

func TestReceivers(t *testing.T) {

	var receivers meta.DeployedReceiverList
	loadListFile(t, "../install/receivers.csv", &receivers)

	t.Run("check for particular receiver installation overlaps", func(t *testing.T) {
		installs := make(map[string]meta.DeployedReceiverList)
		for _, s := range receivers {
			if _, ok := installs[s.Model]; !ok {
				installs[s.Model] = meta.DeployedReceiverList{}
			}
			installs[s.Model] = append(installs[s.Model], s)
		}

		for _, v := range installs {
			for i := 0; i < len(v); i++ {
				for j := i + 1; j < len(v); j++ {
					if v[i].Serial != v[j].Serial {
						continue
					}
					if v[i].End.Before(v[j].Start) {
						continue
					}
					if v[i].Start.After(v[j].End) {
						continue
					}
					if v[i].End.Equal(v[j].Start) {
						continue
					}
					if v[i].Start.Equal(v[j].End) {
						continue
					}

					t.Errorf("receiver %s [%s] at %s has overlap with %s between times %s and %s",
						v[i].Model, v[i].Serial, v[i].Mark, v[j].Mark, v[i].Start.Format(meta.DateTimeFormat), v[i].End.Format(meta.DateTimeFormat))
				}
			}
		}
	})

	t.Run("check for receiver installation equipment overlaps", func(t *testing.T) {
		installs := make(map[string]meta.DeployedReceiverList)
		for _, s := range receivers {
			if _, ok := installs[s.Mark]; !ok {
				installs[s.Mark] = meta.DeployedReceiverList{}
			}
			installs[s.Mark] = append(installs[s.Mark], s)
		}

		for _, v := range installs {
			for i := 0; i < len(v); i++ {
				for j := i + 1; j < len(v); j++ {
					if v[i].End.Before(v[j].Start) {
						continue
					}
					if v[i].Start.After(v[j].End) {
						continue
					}
					if v[i].End.Equal(v[j].Start) {
						continue
					}
					if v[i].Start.Equal(v[j].End) {
						continue
					}

					t.Errorf("receivers %s [%s] / %s [%s] at %s has overlap between %s and %s",
						v[i].Model, v[i].Serial, v[j].Model, v[j].Serial, v[i].Mark, v[i].Start.Format(meta.DateTimeFormat), v[i].End.Format(meta.DateTimeFormat))
				}
			}
		}
	})

	t.Run("check for missing receiver marks", func(t *testing.T) {
		var marks meta.MarkList
		loadListFile(t, "../network/marks.csv", &marks)

		keys := make(map[string]interface{})
		for _, m := range marks {
			keys[m.Code] = true
		}

		for _, r := range receivers {
			if _, ok := keys[r.Mark]; ok {
				continue
			}
			t.Errorf("unable to find receiver mark %-5s", r.Mark)
		}
	})

	t.Run("check for missing receiver assets", func(t *testing.T) {
		var assets meta.AssetList
		loadListFile(t, "../assets/receivers.csv", &assets)
		for _, r := range receivers {
			var found bool
			for _, a := range assets {
				if a.Model != r.Model {
					continue
				}
				if a.Serial != r.Serial {
					continue
				}
				found = true
			}
			if found {
				continue
			}
			t.Errorf("unable to find receiver asset: %s [%s]", r.Model, r.Serial)
		}
	})

}
<|endoftext|>


<commit_before>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// sample-bar demonstrates a sample i3bar built using barista.
package main

import (
	"fmt"
	"io/ioutil"
	"os/exec"
	"os/user"
	"path/filepath"
	"time"

	"github.com/soumya92/barista/bar"
	"github.com/soumya92/barista/modules/clock"
	"github.com/soumya92/barista/modules/cputemp"
	"github.com/soumya92/barista/modules/media"
	"github.com/soumya92/barista/modules/meminfo"
	"github.com/soumya92/barista/modules/netspeed"
	"github.com/soumya92/barista/modules/sysinfo"
	"github.com/soumya92/barista/modules/volume"
	"github.com/soumya92/barista/modules/weather"
	"github.com/soumya92/barista/outputs"
)

func truncate(in string, l int) string {
	if len([]rune(in)) <= l {
		return in
	}
	return string([]rune(in)[:l-1]) + "⋯"
}

func hms(d time.Duration) (h int, m int, s int) {
	h = int(d.Hours())
	m = int(d.Minutes()) % 60
	s = int(d.Seconds()) % 60
	return
}

func formatMediaTime(d time.Duration) string {
	h, m, s := hms(d)
	if h > 0 {
		return fmt.Sprintf("%d:%02d:%02d", h, m, s)
	}
	return fmt.Sprintf("%d:%02d", m, s)
}

func mediaFormatFunc(m media.Info) *bar.Output {
	if m.PlaybackStatus == media.Stopped || m.PlaybackStatus == media.Disconnected {
		return nil
	}
	artist := truncate(m.Artist, 20)
	title := truncate(m.Title, 40-len(artist))
	if len(title) < 20 {
		artist = truncate(m.Artist, 40-len(title))
	}
	if m.PlaybackStatus != media.Playing {
		return outputs.Textf("%s - %s", title, artist)
	}
	return outputs.Textf(
		"%s/%s: %s - %s",
		formatMediaTime(m.Position()),
		formatMediaTime(m.Length),
		title,
		artist,
	)
}

func startTaskManager(e bar.Event) {
	if e.Button == bar.ButtonLeft {
		exec.Command("xfce4-taskmanager").Run()
	}
}

func home(path string) string {
	usr, err := user.Current()
	if err != nil {
		panic(err)
	}
	return filepath.Join(usr.HomeDir, path)
}

func main() {

	localtime := clock.New(clock.OutputFormat("Mon Jan 2 15:04"))
	localtime.OnClick(func(e bar.Event) {
		if e.Button == bar.ButtonLeft {
			exec.Command("gsimplecal").Run()
		}
	})

	apiKey, err := ioutil.ReadFile(home(".config/owm.apikey"))
	if err != nil {
		panic(err)
	}
	wthr := weather.New(
		weather.Zipcode{"94043", "US"},
		weather.APIKey(string(apiKey)),
		weather.OutputTemplate(outputs.TextTemplate(`{{.Description}}, {{.Temperature.C}}℃`)),
	)

	vol := volume.New(
		volume.OutputFunc(func(v volume.Volume) *bar.Output {
			if v.Mute {
				return outputs.Text("MUT")
			}
			return outputs.Textf("%02d%%", v.Pct())
		}),
	)

	loadAvg := sysinfo.New(
		sysinfo.OutputFunc("loadAvg", func(s sysinfo.Info) *bar.Output {
			out := outputs.Textf("%0.2f %0.2f", s.Loads[0], s.Loads[2])
			// Load averages are unusually high for a few minutes after boot.
			if s.Uptime < 10*time.Minute {
				// so don't add colours until 10 minutes after system start.
				return out
			}
			switch {
			case s.Loads[0] > 128, s.Loads[2] > 64:
				out.Urgent = true
			case s.Loads[0] > 64, s.Loads[2] > 32:
				out.Color = bar.Color("red")
			case s.Loads[0] > 32, s.Loads[2] > 16:
				out.Color = bar.Color("yellow")
			}
			return out
		}),
	).Get("loadAvg")
	loadAvg.OnClick(startTaskManager)

	freeMem := meminfo.New(
		meminfo.OutputFunc("freeMem", func(m meminfo.Info) *bar.Output {
			out := outputs.Text(m.Available().IEC())
			freeGigs := m.Available().In("GiB")
			switch {
			case freeGigs < 0.5:
				out.Urgent = true
			case freeGigs < 1:
				out.Color = bar.Color("red")
			case freeGigs < 2:
				out.Color = bar.Color("yellow")
			case freeGigs > 12:
				out.Color = bar.Color("green")
			}
			return out
		}),
	).Get("freeMem")
	freeMem.OnClick(startTaskManager)

	temp := cputemp.New(
		cputemp.RefreshInterval(2*time.Second),
		cputemp.OutputFunc(func(temp cputemp.Temperature) *bar.Output {
			celcius := temp.C()
			out := outputs.Textf("%2d℃", celcius)
			switch {
			case celcius > 90:
				out.Urgent = true
			case celcius > 70:
				out.Color = bar.Color("red")
			case celcius > 60:
				out.Color = bar.Color("yellow")
			}
			return out
		}),
	)

	netspeedTpl := `{{.Tx.SI | printf "%5s"}} {{.Rx.SI | printf "%5s"}}`
	net := netspeed.New(
		netspeed.Interface("em1"),
		netspeed.RefreshInterval(2*time.Second),
		netspeed.OutputTemplate(outputs.TextTemplate(netspeedTpl)),
	)

	rhythmbox := media.New(
		"rhythmbox",
		media.TrackPosition,
		media.OutputFunc(mediaFormatFunc),
	)

	panic(bar.Run(
		rhythmbox,
		net,
		temp,
		freeMem,
		loadAvg,
		vol,
		wthr,
		localtime,
	))
}
<commit_msg>TrimSpace when reading weather API key<commit_after>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// sample-bar demonstrates a sample i3bar built using barista.
package main

import (
	"fmt"
	"io/ioutil"
	"os/exec"
	"os/user"
	"path/filepath"
	"strings"
	"time"

	"github.com/soumya92/barista/bar"
	"github.com/soumya92/barista/modules/clock"
	"github.com/soumya92/barista/modules/cputemp"
	"github.com/soumya92/barista/modules/media"
	"github.com/soumya92/barista/modules/meminfo"
	"github.com/soumya92/barista/modules/netspeed"
	"github.com/soumya92/barista/modules/sysinfo"
	"github.com/soumya92/barista/modules/volume"
	"github.com/soumya92/barista/modules/weather"
	"github.com/soumya92/barista/outputs"
)

func truncate(in string, l int) string {
	if len([]rune(in)) <= l {
		return in
	}
	return string([]rune(in)[:l-1]) + "⋯"
}

func hms(d time.Duration) (h int, m int, s int) {
	h = int(d.Hours())
	m = int(d.Minutes()) % 60
	s = int(d.Seconds()) % 60
	return
}

func formatMediaTime(d time.Duration) string {
	h, m, s := hms(d)
	if h > 0 {
		return fmt.Sprintf("%d:%02d:%02d", h, m, s)
	}
	return fmt.Sprintf("%d:%02d", m, s)
}

func mediaFormatFunc(m media.Info) *bar.Output {
	if m.PlaybackStatus == media.Stopped || m.PlaybackStatus == media.Disconnected {
		return nil
	}
	artist := truncate(m.Artist, 20)
	title := truncate(m.Title, 40-len(artist))
	if len(title) < 20 {
		artist = truncate(m.Artist, 40-len(title))
	}
	if m.PlaybackStatus != media.Playing {
		return outputs.Textf("%s - %s", title, artist)
	}
	return outputs.Textf(
		"%s/%s: %s - %s",
		formatMediaTime(m.Position()),
		formatMediaTime(m.Length),
		title,
		artist,
	)
}

func startTaskManager(e bar.Event) {
	if e.Button == bar.ButtonLeft {
		exec.Command("xfce4-taskmanager").Run()
	}
}

func home(path string) string {
	usr, err := user.Current()
	if err != nil {
		panic(err)
	}
	return filepath.Join(usr.HomeDir, path)
}

func main() {

	localtime := clock.New(clock.OutputFormat("Mon Jan 2 15:04"))
	localtime.OnClick(func(e bar.Event) {
		if e.Button == bar.ButtonLeft {
			exec.Command("gsimplecal").Run()
		}
	})

	apiKey, err := ioutil.ReadFile(home(".config/owm.apikey"))
	if err != nil {
		panic(err)
	}
	wthr := weather.New(
		weather.Zipcode{"94043", "US"},
		weather.APIKey(strings.TrimSpace(string(apiKey))),
		weather.OutputTemplate(outputs.TextTemplate(`{{.Description}}, {{.Temperature.C}}℃`)),
	)

	vol := volume.New(
		volume.OutputFunc(func(v volume.Volume) *bar.Output {
			if v.Mute {
				return outputs.Text("MUT")
			}
			return outputs.Textf("%02d%%", v.Pct())
		}),
	)

	loadAvg := sysinfo.New(
		sysinfo.OutputFunc("loadAvg", func(s sysinfo.Info) *bar.Output {
			out := outputs.Textf("%0.2f %0.2f", s.Loads[0], s.Loads[2])
			// Load averages are unusually high for a few minutes after boot.
			if s.Uptime < 10*time.Minute {
				// so don't add colours until 10 minutes after system start.
				return out
			}
			switch {
			case s.Loads[0] > 128, s.Loads[2] > 64:
				out.Urgent = true
			case s.Loads[0] > 64, s.Loads[2] > 32:
				out.Color = bar.Color("red")
			case s.Loads[0] > 32, s.Loads[2] > 16:
				out.Color = bar.Color("yellow")
			}
			return out
		}),
	).Get("loadAvg")
	loadAvg.OnClick(startTaskManager)

	freeMem := meminfo.New(
		meminfo.OutputFunc("freeMem", func(m meminfo.Info) *bar.Output {
			out := outputs.Text(m.Available().IEC())
			freeGigs := m.Available().In("GiB")
			switch {
			case freeGigs < 0.5:
				out.Urgent = true
			case freeGigs < 1:
				out.Color = bar.Color("red")
			case freeGigs < 2:
				out.Color = bar.Color("yellow")
			case freeGigs > 12:
				out.Color = bar.Color("green")
			}
			return out
		}),
	).Get("freeMem")
	freeMem.OnClick(startTaskManager)

	temp := cputemp.New(
		cputemp.RefreshInterval(2*time.Second),
		cputemp.OutputFunc(func(temp cputemp.Temperature) *bar.Output {
			celcius := temp.C()
			out := outputs.Textf("%2d℃", celcius)
			switch {
			case celcius > 90:
				out.Urgent = true
			case celcius > 70:
				out.Color = bar.Color("red")
			case celcius > 60:
				out.Color = bar.Color("yellow")
			}
			return out
		}),
	)

	netspeedTpl := `{{.Tx.SI | printf "%5s"}} {{.Rx.SI | printf "%5s"}}`
	net := netspeed.New(
		netspeed.Interface("em1"),
		netspeed.RefreshInterval(2*time.Second),
		netspeed.OutputTemplate(outputs.TextTemplate(netspeedTpl)),
	)

	rhythmbox := media.New(
		"rhythmbox",
		media.TrackPosition,
		media.OutputFunc(mediaFormatFunc),
	)

	panic(bar.Run(
		rhythmbox,
		net,
		temp,
		freeMem,
		loadAvg,
		vol,
		wthr,
		localtime,
	))
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package mungers

import (
	"fmt"
	"regexp"
	"strings"

	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/test-infra/mungegithub/features"
	"k8s.io/test-infra/mungegithub/github"
	"k8s.io/test-infra/mungegithub/options"

	"github.com/golang/glog"
	githubapi "github.com/google/go-github/github"
)

var (
	labelPrefixes = []string{"sig/", "committee/", "wg/"}
	sigMentionRE  = regexp.MustCompile(`@\S+\nThere are no sig labels on this issue.`)
)

const needsSigLabel = "needs-sig"

type SigMentionHandler struct{}

func init() {
	h := &SigMentionHandler{}
	RegisterMungerOrDie(h)
	RegisterStaleIssueComments(h)
}

// Name is the name usable in --pr-mungers
func (*SigMentionHandler) Name() string { return "sig-mention-handler" }

// RequiredFeatures is a slice of 'features' that must be provided
func (*SigMentionHandler) RequiredFeatures() []string {
	return []string{}
}

// Initialize will initialize the munger
func (s *SigMentionHandler) Initialize(config *github.Config, features *features.Features) error {
	return nil
}

// EachLoop is called at the start of every munge loop
func (*SigMentionHandler) EachLoop() error { return nil }

// RegisterOptions registers options for this munger; returns any that require a restart when changed.
func (*SigMentionHandler) RegisterOptions(opts *options.Options) sets.String { return nil }

func (*SigMentionHandler) HasSigLabel(obj *github.MungeObject) bool {
	labels := obj.Issue.Labels

	for i := range labels {
		if labels[i].Name == nil {
			continue
		}
		for j := range labelPrefixes {
			if strings.HasPrefix(*labels[i].Name, labelPrefixes[j]) {
				return true
			}
		}
	}

	return false
}

func (*SigMentionHandler) HasNeedsSigLabel(obj *github.MungeObject) bool {
	labels := obj.Issue.Labels

	for i := range labels {
		if labels[i].Name != nil && strings.Compare(*labels[i].Name, needsSigLabel) == 0 {
			return true
		}
	}

	return false
}

// Munge is the workhorse notifying issue owner to add a @kubernetes/sig mention if there is none
// The algorithm:
// (1) return if it is a PR and/or the issue is closed
// (2) find if the issue has a sig label
// (3) find if the issue has a needs-sig label
// (4) if the issue has both the sig and needs-sig labels, remove the needs-sig label
// (5) if the issue has none of the labels, add the needs-sig label and comment
// (6) if the issue has only the sig label, do nothing
// (7) if the issue has only the needs-sig label, do nothing
func (s *SigMentionHandler) Munge(obj *github.MungeObject) {
	if obj.Issue == nil || obj.IsPR() || obj.Issue.State == nil || *obj.Issue.State == "closed" {
		return
	}

	hasSigLabel := s.HasSigLabel(obj)
	hasNeedsSigLabel := s.HasNeedsSigLabel(obj)

	if hasSigLabel && hasNeedsSigLabel {
		if err := obj.RemoveLabel(needsSigLabel); err != nil {
			glog.Errorf("failed to remove needs-sig label for issue #%d", *obj.Issue.Number)
		}
	} else if !hasSigLabel && !hasNeedsSigLabel {
		if err := obj.AddLabel(needsSigLabel); err != nil {
			glog.Errorf("failed to add needs-sig label for issue #%d", *obj.Issue.Number)
			return
		}

		msg := fmt.Sprintf(`@%s
There are no sig labels on this issue. Please [add a sig label](https://github.com/kubernetes/test-infra/blob/master/commands.md) by:

1. mentioning a sig: `+"`@kubernetes/sig-<group-name>-<group-suffix>`"+`
    e.g., `+"`@kubernetes/sig-contributor-experience-<group-suffix>`"+` to notify the contributor experience sig, OR

2. specifying the label manually: `+"`/sig <label>`"+`
    e.g., `+"`/sig scalability`"+` to apply the `+"`sig/scalability`"+` label

Note: Method 1 will trigger an email to the group. You can find the group list [here](https://github.com/kubernetes/community/blob/master/sig-list.md) and label list [here](https://github.com/kubernetes/kubernetes/labels).
The `+"`<group-suffix>`"+` in the method 1 has to be replaced with one of these: _**bugs, feature-requests, pr-reviews, test-failures, proposals**_`, *obj.Issue.User.Login)

		if err := obj.WriteComment(msg); err != nil {
			glog.Errorf("failed to leave comment for %s that issue #%d needs sig label", *obj.Issue.User.Login, *obj.Issue.Number)
		}
	}
}

// isStaleIssueComment determines if a comment is stale based on if the issue has the needs-sig label
func (*SigMentionHandler) isStaleIssueComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool {
	if !obj.IsRobot(comment.User) {
		return false
	}
	if !sigMentionRE.MatchString(*comment.Body) {
		return false
	}
	stale := !obj.HasLabel(needsSigLabel)
	if stale {
		glog.V(6).Infof("Found stale SigMentionHandler comment")
	}
	return stale
}

// StaleIssueComments returns a slice of stale issue comments.
func (s *SigMentionHandler) StaleIssueComments(obj *github.MungeObject, comments []*githubapi.IssueComment) []*githubapi.IssueComment {
	return forEachCommentTest(obj, comments, s.isStaleIssueComment)
}
<commit_msg>Do not use "here" as link text.<commit_after>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package mungers

import (
	"fmt"
	"regexp"
	"strings"

	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/test-infra/mungegithub/features"
	"k8s.io/test-infra/mungegithub/github"
	"k8s.io/test-infra/mungegithub/options"

	"github.com/golang/glog"
	githubapi "github.com/google/go-github/github"
)

var (
	labelPrefixes = []string{"sig/", "committee/", "wg/"}
	sigMentionRE  = regexp.MustCompile(`@\S+\nThere are no sig labels on this issue.`)
)

const needsSigLabel = "needs-sig"

type SigMentionHandler struct{}

func init() {
	h := &SigMentionHandler{}
	RegisterMungerOrDie(h)
	RegisterStaleIssueComments(h)
}

// Name is the name usable in --pr-mungers
func (*SigMentionHandler) Name() string { return "sig-mention-handler" }

// RequiredFeatures is a slice of 'features' that must be provided
func (*SigMentionHandler) RequiredFeatures() []string {
	return []string{}
}

// Initialize will initialize the munger
func (s *SigMentionHandler) Initialize(config *github.Config, features *features.Features) error {
	return nil
}

// EachLoop is called at the start of every munge loop
func (*SigMentionHandler) EachLoop() error { return nil }

// RegisterOptions registers options for this munger; returns any that require a restart when changed.
func (*SigMentionHandler) RegisterOptions(opts *options.Options) sets.String { return nil }

func (*SigMentionHandler) HasSigLabel(obj *github.MungeObject) bool {
	labels := obj.Issue.Labels

	for i := range labels {
		if labels[i].Name == nil {
			continue
		}
		for j := range labelPrefixes {
			if strings.HasPrefix(*labels[i].Name, labelPrefixes[j]) {
				return true
			}
		}
	}

	return false
}

func (*SigMentionHandler) HasNeedsSigLabel(obj *github.MungeObject) bool {
	labels := obj.Issue.Labels

	for i := range labels {
		if labels[i].Name != nil && strings.Compare(*labels[i].Name, needsSigLabel) == 0 {
			return true
		}
	}

	return false
}

// Munge is the workhorse notifying issue owner to add a @kubernetes/sig mention if there is none
// The algorithm:
// (1) return if it is a PR and/or the issue is closed
// (2) find if the issue has a sig label
// (3) find if the issue has a needs-sig label
// (4) if the issue has both the sig and needs-sig labels, remove the needs-sig label
// (5) if the issue has none of the labels, add the needs-sig label and comment
// (6) if the issue has only the sig label, do nothing
// (7) if the issue has only the needs-sig label, do nothing
func (s *SigMentionHandler) Munge(obj *github.MungeObject) {
	if obj.Issue == nil || obj.IsPR() || obj.Issue.State == nil || *obj.Issue.State == "closed" {
		return
	}

	hasSigLabel := s.HasSigLabel(obj)
	hasNeedsSigLabel := s.HasNeedsSigLabel(obj)

	if hasSigLabel && hasNeedsSigLabel {
		if err := obj.RemoveLabel(needsSigLabel); err != nil {
			glog.Errorf("failed to remove needs-sig label for issue #%d", *obj.Issue.Number)
		}
	} else if !hasSigLabel && !hasNeedsSigLabel {
		if err := obj.AddLabel(needsSigLabel); err != nil {
			glog.Errorf("failed to add needs-sig label for issue #%d", *obj.Issue.Number)
			return
		}

		msg := fmt.Sprintf(`@%s
There are no sig labels on this issue. Please [add a sig label](https://github.com/kubernetes/test-infra/blob/master/commands.md) by:

1. mentioning a sig: `+"`@kubernetes/sig-<group-name>-<group-suffix>`"+`
    e.g., `+"`@kubernetes/sig-contributor-experience-<group-suffix>`"+` to notify the contributor experience sig, OR

2. specifying the label manually: `+"`/sig <label>`"+`
    e.g., `+"`/sig scalability`"+` to apply the `+"`sig/scalability`"+` label

Note: Method 1 will trigger an email to the group. See the [group list](https://github.com/kubernetes/community/blob/master/sig-list.md) and [label list](https://github.com/kubernetes/kubernetes/labels).
The `+"`<group-suffix>`"+` in the method 1 has to be replaced with one of these: _**bugs, feature-requests, pr-reviews, test-failures, proposals**_`, *obj.Issue.User.Login)

		if err := obj.WriteComment(msg); err != nil {
			glog.Errorf("failed to leave comment for %s that issue #%d needs sig label", *obj.Issue.User.Login, *obj.Issue.Number)
		}
	}
}

// isStaleIssueComment determines if a comment is stale based on if the issue has the needs-sig label
func (*SigMentionHandler) isStaleIssueComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool {
	if !obj.IsRobot(comment.User) {
		return false
	}
	if !sigMentionRE.MatchString(*comment.Body) {
		return false
	}
	stale := !obj.HasLabel(needsSigLabel)
	if stale {
		glog.V(6).Infof("Found stale SigMentionHandler comment")
	}
	return stale
}

// StaleIssueComments returns a slice of stale issue comments.
func (s *SigMentionHandler) StaleIssueComments(obj *github.MungeObject, comments []*githubapi.IssueComment) []*githubapi.IssueComment {
	return forEachCommentTest(obj, comments, s.isStaleIssueComment)
}
<|endoftext|>