<commit_before>package auth

import (
	"encoding/json"
	"mime"
	"net/http"

	"github.com/gorilla/schema"
)

var formDecoder = schema.NewDecoder()

// TODO support user defined expiration
type Credentials struct {
	UserName string `json:"username" schema:"username"`
	Password string `json:"password" schema:"password"`
}

type LoginResponse struct {
	Token     string    `json:"token"`
	UserID    string    `json:"user_id"`
	UserName  string    `json:"user_name"`
	ExpiredAt Timestamp `json:"expired_at"`
}

func LoginHandler(config *Config) http.Handler {
	config = config.setDefaults()
	return http.HandlerFunc(LoginHandlerFunc(config))
}

func LoginHandlerFunc(config *Config) http.HandlerFunc {
	config = config.setDefaults()

	return func(w http.ResponseWriter, r *http.Request) {
		cred, err1 := decodeCredentials(w, r)
		if err1 != nil {
			SendError(w, err1)
			return
		}

		user, err2 := config.UserStore.ValidateCredentials(r.Context(), cred.UserName, cred.Password)
		if err2 != nil {
			SendError(w, ErrBadCredentials.WithCause(err2))
			return
		}

		issuedAt := now()
		token := &Token{
			UserID:    user.GetID(),
			UserName:  user.GetName(),
			IssuedAt:  Timestamp(issuedAt),
			ExpiredAt: Timestamp(issuedAt.Add(config.TokenExpiration)),
			ClientIP:  getClientIP(r),
			Claims:    user.GetClaims(),
		}

		tokenString, err3 := token.Encode(config)
		if err3 != nil {
			SendError(w, err3)
			return
		}

		SendJSON(w, &LoginResponse{
			Token:     tokenString,
			UserID:    token.UserID,
			UserName:  token.UserName,
			ExpiredAt: token.ExpiredAt,
		})
	}
}

func decodeCredentials(w http.ResponseWriter, r *http.Request) (*Credentials, *Error) {
	if len(r.Header.Get(authorizationHeader)) > 0 {
		username, password, ok := r.BasicAuth()
		if !ok {
			return nil, ErrBadAuthorizationHeader
		}
		return &Credentials{username, password}, nil
	}

	result := &Credentials{}
	err := decodePayload(w, r, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func decodePayload(w http.ResponseWriter, r *http.Request, payload interface{}) *Error {
	contentType := r.Header.Get("Content-Type")
	mediaType, _, err := mime.ParseMediaType(contentType)
	if err != nil {
		return ErrUnsupportedContentType.WithCause(err)
	}

	if mediaType == contentJSON {
		err = json.NewDecoder(r.Body).Decode(payload)
		if err != nil {
			return ErrMalformedContent.WithCause(err)
		}
		return nil
	}

	if mediaType == contentForm {
		err = r.ParseForm()
		if err != nil {
			return ErrUnsupportedContentType.WithCause(err)
		}
		err = formDecoder.Decode(payload, r.PostForm)
		if err != nil {
			return ErrMalformedContent.WithCause(err)
		}
		return nil
	}

	return ErrUnsupportedContentType
}
<commit_msg>expose WriteLoginResponse<commit_after>package auth

import (
	"encoding/json"
	"mime"
	"net/http"

	"github.com/gorilla/schema"
)

var formDecoder = schema.NewDecoder()

// TODO support user defined expiration
type Credentials struct {
	UserName string `json:"username" schema:"username"`
	Password string `json:"password" schema:"password"`
}

type LoginResponse struct {
	Token     string    `json:"token"`
	UserID    string    `json:"user_id"`
	UserName  string    `json:"user_name"`
	ExpiredAt Timestamp `json:"expired_at"`
}

func LoginHandler(config *Config) http.Handler {
	config = config.setDefaults()
	return http.HandlerFunc(LoginHandlerFunc(config))
}

func LoginHandlerFunc(config *Config) http.HandlerFunc {
	config = config.setDefaults()

	return func(w http.ResponseWriter, r *http.Request) {
		cred, err1 := decodeCredentials(w, r)
		if err1 != nil {
			SendError(w, err1)
			return
		}

		user, err2 := config.UserStore.ValidateCredentials(r.Context(), cred.UserName, cred.Password)
		if err2 != nil {
			SendError(w, ErrBadCredentials.WithCause(err2))
			return
		}

		WriteLoginResponse(w, r, config, user)
	}
}

func WriteLoginResponse(w http.ResponseWriter, r *http.Request, config *Config, user User) {
	issuedAt := now()
	token := &Token{
		UserID:    user.GetID(),
		UserName:  user.GetName(),
		IssuedAt:  Timestamp(issuedAt),
		ExpiredAt: Timestamp(issuedAt.Add(config.TokenExpiration)),
		ClientIP:  getClientIP(r),
		Claims:    user.GetClaims(),
	}

	tokenString, err3 := token.Encode(config)
	if err3 != nil {
		SendError(w, err3)
		return
	}

	SendJSON(w, &LoginResponse{
		Token:     tokenString,
		UserID:    token.UserID,
		UserName:  token.UserName,
		ExpiredAt: token.ExpiredAt,
	})
}

func decodeCredentials(w http.ResponseWriter, r *http.Request) (*Credentials, *Error) {
	if len(r.Header.Get(authorizationHeader)) > 0 {
		username, password, ok := r.BasicAuth()
		if !ok {
			return nil, ErrBadAuthorizationHeader
		}
		return &Credentials{username, password}, nil
	}

	result := &Credentials{}
	err := decodePayload(w, r, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func decodePayload(w http.ResponseWriter, r *http.Request, payload interface{}) *Error {
	contentType := r.Header.Get("Content-Type")
	mediaType, _, err := mime.ParseMediaType(contentType)
	if err != nil {
		return ErrUnsupportedContentType.WithCause(err)
	}

	if mediaType == contentJSON {
		err = json.NewDecoder(r.Body).Decode(payload)
		if err != nil {
			return ErrMalformedContent.WithCause(err)
		}
		return nil
	}

	if mediaType == contentForm {
		err = r.ParseForm()
		if err != nil {
			return ErrUnsupportedContentType.WithCause(err)
		}
		err = formDecoder.Decode(payload, r.PostForm)
		if err != nil {
			return ErrMalformedContent.WithCause(err)
		}
		return nil
	}

	return ErrUnsupportedContentType
}
<|endoftext|>


<commit_before>package logit

import (
	"io"
	"strconv"
	"sync"
	"time"
)

// A Logger represents an active logging object that generates lines of output to an io.Writer. Each logging operation
// makes a single call to the Writer's Write method. A Logger can be used simultaneously from multiple goroutines; it
// guarantees to serialize access to the Writer.
type Logger struct {
	mu     *sync.Mutex            // ensures atomic writes; protects the following fields
	out    io.Writer              // destination for output
	sys    string                 // the sub-system to write at beginning of each line
	fields map[string]interface{} // the fields to also log
}

func New(out io.Writer, sys string) *Logger {
	mu := sync.Mutex{}
	return &Logger{mu: &mu, out: out, sys: sys, fields: make(map[string]interface{})}
}

// Clone returns a new Logger which uses the same output but which has the system set to "<current>.system". e.g. if
// you have a logger where has the "main" system, cloning it with the system "datastore" will result in the system
// field being "main.datastore".
//
// Since the cloned logger uses the same lock as the original, use of this lock also garantees serial access to the
// Writer and therefore can also be used in multiple goroutines.
func (l *Logger) Clone(sys string) *Logger {
	l.mu.Lock()
	defer l.mu.Unlock()

	// clone the l.fields here
	m := make(map[string]interface{})
	for k, v := range l.fields {
		m[k] = v
	}

	return &Logger{mu: l.mu, out: l.out, sys: l.sys + "." + sys, fields: m}
}

func (l *Logger) WithField(key string, value interface{}) {
	if key == "time" {
		panic("logit: key=time is not allowed")
	}
	if key == "sys" {
		panic("logit: key=sys is not allowed")
	}
	if key == "msg" {
		panic("logit: key=msg is not allowed")
	}
	if key == "err" {
		panic("logit: key=err is not allowed")
	}

	l.mu.Lock()
	defer l.mu.Unlock()
	l.fields[key] = value
}

// Log just logs a message to the output. It doesn't do anything special.
func (l *Logger) Log(msg string) error {
	return l.Output(msg)
}

// Output writes the output for a logging event.
func (l *Logger) Output(msg string) error {
	l.mu.Lock()
	defer l.mu.Unlock()

	// time
	str := "time="
	str += time.Now().UTC().Format("20060102-150405.000000000")
	str += " "

	// sys
	str += "sys=" + l.sys + " "

	// now do all of the fields
	for k, v := range l.fields {
		switch vv := v.(type) {
		case string:
			// ToDo: currently presuming everything is a string
			str += k + "=" + vv + " "
		case int:
			str += k + "=" + strconv.Itoa(vv) + " "
		case time.Duration:
			str += k + "=" + vv.String() + " "
		default:
			str += k + "=" + "(unknown type) "
		}
	}

	// message
	str += "msg=" + msg

	// newline
	str += "\n"

	_, err := l.out.Write([]byte(str))
	return err
}

// // SetOutput sets the output destination for the logger.
// func (l *Logger) SetOutput(w io.Writer) {
// 	l.mu.Lock()
// 	defer l.mu.Unlock()
// 	l.out = w
// }
<commit_msg>Add Fatal() so we can quit too<commit_after>package logit

import (
	"io"
	"os"
	"strconv"
	"sync"
	"time"
)

// A Logger represents an active logging object that generates lines of output to an io.Writer. Each logging operation
// makes a single call to the Writer's Write method. A Logger can be used simultaneously from multiple goroutines; it
// guarantees to serialize access to the Writer.
type Logger struct {
	mu     *sync.Mutex            // ensures atomic writes; protects the following fields
	out    io.Writer              // destination for output
	sys    string                 // the sub-system to write at beginning of each line
	fields map[string]interface{} // the fields to also log
}

func New(out io.Writer, sys string) *Logger {
	mu := sync.Mutex{}
	return &Logger{mu: &mu, out: out, sys: sys, fields: make(map[string]interface{})}
}

// Clone returns a new Logger which uses the same output but which has the system set to "<current>.system". e.g. if
// you have a logger where has the "main" system, cloning it with the system "datastore" will result in the system
// field being "main.datastore".
//
// Since the cloned logger uses the same lock as the original, use of this lock also garantees serial access to the
// Writer and therefore can also be used in multiple goroutines.
func (l *Logger) Clone(sys string) *Logger {
	l.mu.Lock()
	defer l.mu.Unlock()

	// clone the l.fields here
	m := make(map[string]interface{})
	for k, v := range l.fields {
		m[k] = v
	}

	return &Logger{mu: l.mu, out: l.out, sys: l.sys + "." + sys, fields: m}
}

func (l *Logger) WithField(key string, value interface{}) {
	if key == "time" {
		panic("logit: key=time is not allowed")
	}
	if key == "sys" {
		panic("logit: key=sys is not allowed")
	}
	if key == "msg" {
		panic("logit: key=msg is not allowed")
	}
	if key == "err" {
		panic("logit: key=err is not allowed")
	}

	l.mu.Lock()
	defer l.mu.Unlock()
	l.fields[key] = value
}

// Log just logs a message to the output. It doesn't do anything special.
func (l *Logger) Log(msg string) error {
	return l.Output(msg)
}

// Fatal is equivalent to Log() followed by a call to os.Exit(1).
func (l *Logger) Fatal(msg string) {
	l.Output(msg)
	os.Exit(1)
}

// Output writes the output for a logging event.
func (l *Logger) Output(msg string) error {
	l.mu.Lock()
	defer l.mu.Unlock()

	// time
	str := "time="
	str += time.Now().UTC().Format("20060102-150405.000000000")
	str += " "

	// sys
	str += "sys=" + l.sys + " "

	// now do all of the fields
	for k, v := range l.fields {
		switch vv := v.(type) {
		case string:
			// ToDo: currently presuming everything is a string
			str += k + "=" + vv + " "
		case int:
			str += k + "=" + strconv.Itoa(vv) + " "
		case time.Duration:
			str += k + "=" + vv.String() + " "
		default:
			str += k + "=" + "(unknown type) "
		}
	}

	// message
	str += "msg=" + msg

	// newline
	str += "\n"

	_, err := l.out.Write([]byte(str))
	return err
}

// // SetOutput sets the output destination for the logger.
// func (l *Logger) SetOutput(w io.Writer) {
// 	l.mu.Lock()
// 	defer l.mu.Unlock()
// 	l.out = w
// }
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"testing"
	"time"

	consumergroup "github.com/meitu/go-consumergroup"

	"github.com/Shopify/sarama"
	"github.com/meitu/zk_wrapper"
)

func TestRebalance(t *testing.T) {
	group := genRandomGroupID(10)
	consumers := make([]*consumergroup.ConsumerGroup, 0)
	for i := 0; i < 3; i++ {
		go func() {
			c, err := createConsumerInstance(zookeepers, group, topic)
			if err != nil {
				t.Errorf("Failed to create consumer instance, err %s", err)
			}
			consumers = append(consumers, c)
		}()
	}
	time.Sleep(3 * time.Second) // we have no way to know if the consumer is ready

	kafkaCli, _ := sarama.NewClient(brokers, nil)
	partitions, err := kafkaCli.Partitions(topic)
	if err != nil {
		t.Errorf("Failed to get partitons, err %s", err)
	}
	owners := make([]string, 0)
	zkCli, _, err := zk_wrapper.Connect(zookeepers, 6*time.Second)
	if err != nil {
		t.Fatal("Failed to connect zookeeper")
	}
	for i := 0; i < len(partitions); i++ {
		ownerPath := fmt.Sprintf("/consumers/%s/owners/%s/%d", group, topic, i)
		data, _, err := zkCli.Get(ownerPath)
		if err != nil {
			t.Errorf("Failed to get partition owner, err %s", err)
		}
		owners = append(owners, string(data))
	}
	if len(owners) != len(partitions) {
		t.Errorf("Missing owner in some partitions expected %d, but got %d",
			len(partitions), len(owners))
	}
	for i := 1; i < len(owners); i++ {
		if owners[i] == owners[i-1] {
			t.Fatal("Partition owner should be difference while consumer > 1")
		}
	}

	for _, c := range consumers {
		c.ExitGroup()
	}
}
<commit_msg>FIX: rebalance_test can't exit group while the consumer is rebalancing<commit_after>package main

import (
	"fmt"
	"testing"
	"time"

	consumergroup "github.com/meitu/go-consumergroup"

	"github.com/Shopify/sarama"
	"github.com/meitu/zk_wrapper"
)

func TestRebalance(t *testing.T) {
	group := genRandomGroupID(10)
	consumers := make([]*consumergroup.ConsumerGroup, 0)
	for i := 0; i < 3; i++ {
		go func() {
			c, err := createConsumerInstance(zookeepers, group, topic)
			if err != nil {
				t.Errorf("Failed to create consumer instance, err %s", err)
			}
			consumers = append(consumers, c)
		}()
	}
	time.Sleep(3 * time.Second) // we have no way to know if the consumer is ready

	kafkaCli, _ := sarama.NewClient(brokers, nil)
	partitions, err := kafkaCli.Partitions(topic)
	if err != nil {
		t.Errorf("Failed to get partitons, err %s", err)
	}
	owners := make([]string, 0)
	zkCli, _, err := zk_wrapper.Connect(zookeepers, 6*time.Second)
	if err != nil {
		t.Fatal("Failed to connect zookeeper")
	}
	for i := 0; i < len(partitions); i++ {
		ownerPath := fmt.Sprintf("/consumers/%s/owners/%s/%d", group, topic, i)
		data, _, err := zkCli.Get(ownerPath)
		if err != nil {
			t.Errorf("Failed to get partition owner, err %s", err)
		}
		owners = append(owners, string(data))
	}
	if len(owners) != len(partitions) {
		t.Errorf("Missing owner in some partitions expected %d, but got %d",
			len(partitions), len(owners))
	}
	for i := 1; i < len(owners); i++ {
		if owners[i] == owners[i-1] {
			t.Fatal("Partition owner should be difference while consumer > 1")
		}
	}

	for _, c := range consumers {
		c.ExitGroup()
		// no way to exit group when the consumer is rebalancing
		time.Sleep(3 * time.Second)
	}
}
<|endoftext|>


<commit_before>// eventmeter - generic system to subscribe to events and record their frequency.
package eventmeter

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	metrics "github.com/rcrowley/go-metrics"
	client "github.com/tendermint/tendermint/rpc/lib/client"
	"github.com/tendermint/tmlibs/events"
	"github.com/tendermint/tmlibs/log"
)

const (
	// Get ping/pong latency and call LatencyCallbackFunc with this period.
	latencyPeriod = 1 * time.Second

	// Check if the WS client is connected every
	connectionCheckPeriod = 100 * time.Millisecond
)

// EventMetric exposes metrics for an event.
type EventMetric struct {
	ID          string    `json:"id"`
	Started     time.Time `json:"start_time"`
	LastHeard   time.Time `json:"last_heard"`
	MinDuration int64     `json:"min_duration"`
	MaxDuration int64     `json:"max_duration"`

	// tracks event count and rate
	meter metrics.Meter

	// filled in from the Meter
	Count    int64   `json:"count"`
	Rate1    float64 `json:"rate_1" wire:"unsafe"`
	Rate5    float64 `json:"rate_5" wire:"unsafe"`
	Rate15   float64 `json:"rate_15" wire:"unsafe"`
	RateMean float64 `json:"rate_mean" wire:"unsafe"`

	// so the event can have effects in the eventmeter's consumer. runs in a go
	// routine.
	callback EventCallbackFunc
}

func (metric *EventMetric) Copy() *EventMetric {
	metricCopy := *metric
	metricCopy.meter = metric.meter.Snapshot()
	return &metricCopy
}

// called on GetMetric
func (metric *EventMetric) fillMetric() *EventMetric {
	metric.Count = metric.meter.Count()
	metric.Rate1 = metric.meter.Rate1()
	metric.Rate5 = metric.meter.Rate5()
	metric.Rate15 = metric.meter.Rate15()
	metric.RateMean = metric.meter.RateMean()
	return metric
}

// EventCallbackFunc is a closure to enable side effects from receiving an
// event.
type EventCallbackFunc func(em *EventMetric, data interface{})

// EventUnmarshalFunc is a closure to get the query and data out of the raw
// JSON received over the RPC WebSocket.
type EventUnmarshalFunc func(b json.RawMessage) (string, events.EventData, error)

// LatencyCallbackFunc is a closure to enable side effects from receiving a latency.
type LatencyCallbackFunc func(meanLatencyNanoSeconds float64)

// DisconnectCallbackFunc is a closure to notify a consumer that the connection
// has died.
type DisconnectCallbackFunc func()

// EventMeter tracks events, reports latency and disconnects.
type EventMeter struct {
	wsc *client.WSClient

	mtx    sync.Mutex
	queries map[string]*EventMetric

	unmarshalEvent     EventUnmarshalFunc
	latencyCallback    LatencyCallbackFunc
	disconnectCallback DisconnectCallbackFunc
	subscribed         bool

	quit chan struct{}

	logger log.Logger
}

func NewEventMeter(addr string, unmarshalEvent EventUnmarshalFunc) *EventMeter {
	return &EventMeter{
		wsc:            client.NewWSClient(addr, "/websocket", client.PingPeriod(1*time.Second)),
		queries:         make(map[string]*EventMetric),
		unmarshalEvent: unmarshalEvent,
		logger:         log.NewNopLogger(),
	}
}

// SetLogger lets you set your own logger.
func (em *EventMeter) SetLogger(l log.Logger) {
	em.logger = l
	em.wsc.SetLogger(l.With("module", "rpcclient"))
}

// String returns a string representation of event meter.
func (em *EventMeter) String() string {
	return em.wsc.Address
}

// Start boots up event meter.
func (em *EventMeter) Start() error {
	if err := em.wsc.Start(); err != nil {
		return err
	}

	em.quit = make(chan struct{})
	go em.receiveRoutine()
	go em.disconnectRoutine()

	err := em.subscribe()
	if err != nil {
		return err
	}
	em.subscribed = true
	return nil
}

// Stop stops event meter.
func (em *EventMeter) Stop() {
	close(em.quit)

	if em.wsc.IsRunning() {
		em.wsc.Stop()
	}
}

// Subscribe for the given query. Callback function will be called upon
// receiving an event.
func (em *EventMeter) Subscribe(query string, cb EventCallbackFunc) error {
	em.mtx.Lock()
	defer em.mtx.Unlock()

	if err := em.wsc.Subscribe(context.TODO(), query); err != nil {
		return err
	}

	metric := &EventMetric{
		meter:    metrics.NewMeter(),
		callback: cb,
	}
	em.queries[query] = metric
	return nil
}

// Unsubscribe from the given query.
func (em *EventMeter) Unsubscribe(query string) error {
	em.mtx.Lock()
	defer em.mtx.Unlock()
	if err := em.wsc.Unsubscribe(context.TODO(), query); err != nil {
		return err
	}

	return nil
}

// GetMetric fills in the latest data for an query and return a copy.
func (em *EventMeter) GetMetric(query string) (*EventMetric, error) {
	em.mtx.Lock()
	defer em.mtx.Unlock()
	metric, ok := em.queries[query]
	if !ok {
		return nil, fmt.Errorf("unknown query: %s", query)
	}
	return metric.fillMetric().Copy(), nil
}

// RegisterLatencyCallback allows you to set latency callback.
func (em *EventMeter) RegisterLatencyCallback(f LatencyCallbackFunc) {
	em.mtx.Lock()
	defer em.mtx.Unlock()
	em.latencyCallback = f
}

// RegisterDisconnectCallback allows you to set disconnect callback.
func (em *EventMeter) RegisterDisconnectCallback(f DisconnectCallbackFunc) {
	em.mtx.Lock()
	defer em.mtx.Unlock()
	em.disconnectCallback = f
}

///////////////////////////////////////////////////////////////////////////////
// Private

func (em *EventMeter) subscribe() error {
	for query, _ := range em.queries {
		if err := em.wsc.Subscribe(context.TODO(), query); err != nil {
			return err
		}
	}
	return nil
}

func (em *EventMeter) receiveRoutine() {
	latencyTicker := time.NewTicker(latencyPeriod)
	for {
		select {
		case resp := <-em.wsc.ResponsesCh:
			if resp.Error != nil {
				em.logger.Error("expected some event, got error", "err", resp.Error.Error())
				continue
			}
			query, data, err := em.unmarshalEvent(resp.Result)
			if err != nil {
				em.logger.Error("failed to unmarshal event", "err", err)
				continue
			}
			if query != "" { // FIXME how can it be an empty string?
				em.updateMetric(query, data)
			}
		case <-latencyTicker.C:
			if em.wsc.IsActive() {
				em.callLatencyCallback(em.wsc.PingPongLatencyTimer.Mean())
			}
		case <-em.wsc.Quit():
			return
		case <-em.quit:
			return
		}
	}
}

func (em *EventMeter) disconnectRoutine() {
	ticker := time.NewTicker(connectionCheckPeriod)
	for {
		select {
		case <-ticker.C:
			if em.wsc.IsReconnecting() && em.subscribed { // notify user about disconnect only once
				em.callDisconnectCallback()
				em.subscribed = false
			} else if !em.wsc.IsReconnecting() && !em.subscribed { // resubscribe
				em.subscribe()
				em.subscribed = true
			}
		case <-em.wsc.Quit():
			return
		case <-em.quit:
			return
		}
	}
}

func (em *EventMeter) updateMetric(query string, data events.EventData) {
	em.mtx.Lock()
	defer em.mtx.Unlock()

	metric, ok := em.queries[query]
	if !ok {
		// we already unsubscribed, or got an unexpected query
		return
	}

	last := metric.LastHeard
	metric.LastHeard = time.Now()
	metric.meter.Mark(1)
	dur := int64(metric.LastHeard.Sub(last))
	if dur < metric.MinDuration {
		metric.MinDuration = dur
	}
	if !last.IsZero() && dur > metric.MaxDuration {
		metric.MaxDuration = dur
	}

	if metric.callback != nil {
		go metric.callback(metric.Copy(), data)
	}
}

func (em *EventMeter) callDisconnectCallback() {
	em.mtx.Lock()
	if em.disconnectCallback != nil {
		go em.disconnectCallback()
	}
	em.mtx.Unlock()
}

func (em *EventMeter) callLatencyCallback(meanLatencyNanoSeconds float64) {
	em.mtx.Lock()
	if em.latencyCallback != nil {
		go em.latencyCallback(meanLatencyNanoSeconds)
	}
	em.mtx.Unlock()
}
<commit_msg>Rename queries to queryToMetricMap<commit_after>// eventmeter - generic system to subscribe to events and record their frequency.
package eventmeter

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	metrics "github.com/rcrowley/go-metrics"
	client "github.com/tendermint/tendermint/rpc/lib/client"
	"github.com/tendermint/tmlibs/events"
	"github.com/tendermint/tmlibs/log"
)

const (
	// Get ping/pong latency and call LatencyCallbackFunc with this period.
	latencyPeriod = 1 * time.Second

	// Check if the WS client is connected every
	connectionCheckPeriod = 100 * time.Millisecond
)

// EventMetric exposes metrics for an event.
type EventMetric struct {
	ID          string    `json:"id"`
	Started     time.Time `json:"start_time"`
	LastHeard   time.Time `json:"last_heard"`
	MinDuration int64     `json:"min_duration"`
	MaxDuration int64     `json:"max_duration"`

	// tracks event count and rate
	meter metrics.Meter

	// filled in from the Meter
	Count    int64   `json:"count"`
	Rate1    float64 `json:"rate_1" wire:"unsafe"`
	Rate5    float64 `json:"rate_5" wire:"unsafe"`
	Rate15   float64 `json:"rate_15" wire:"unsafe"`
	RateMean float64 `json:"rate_mean" wire:"unsafe"`

	// so the event can have effects in the eventmeter's consumer. runs in a go
	// routine.
	callback EventCallbackFunc
}

func (metric *EventMetric) Copy() *EventMetric {
	metricCopy := *metric
	metricCopy.meter = metric.meter.Snapshot()
	return &metricCopy
}

// called on GetMetric
func (metric *EventMetric) fillMetric() *EventMetric {
	metric.Count = metric.meter.Count()
	metric.Rate1 = metric.meter.Rate1()
	metric.Rate5 = metric.meter.Rate5()
	metric.Rate15 = metric.meter.Rate15()
	metric.RateMean = metric.meter.RateMean()
	return metric
}

// EventCallbackFunc is a closure to enable side effects from receiving an
// event.
type EventCallbackFunc func(em *EventMetric, data interface{})

// EventUnmarshalFunc is a closure to get the query and data out of the raw
// JSON received over the RPC WebSocket.
type EventUnmarshalFunc func(b json.RawMessage) (string, events.EventData, error)

// LatencyCallbackFunc is a closure to enable side effects from receiving a latency.
type LatencyCallbackFunc func(meanLatencyNanoSeconds float64)

// DisconnectCallbackFunc is a closure to notify a consumer that the connection
// has died.
type DisconnectCallbackFunc func()

// EventMeter tracks events, reports latency and disconnects.
type EventMeter struct {
	wsc *client.WSClient

	mtx    sync.Mutex
	queryToMetricMap map[string]*EventMetric

	unmarshalEvent     EventUnmarshalFunc
	latencyCallback    LatencyCallbackFunc
	disconnectCallback DisconnectCallbackFunc
	subscribed         bool

	quit chan struct{}

	logger log.Logger
}

func NewEventMeter(addr string, unmarshalEvent EventUnmarshalFunc) *EventMeter {
	return &EventMeter{
		wsc:            client.NewWSClient(addr, "/websocket", client.PingPeriod(1*time.Second)),
		queryToMetricMap:         make(map[string]*EventMetric),
		unmarshalEvent: unmarshalEvent,
		logger:         log.NewNopLogger(),
	}
}

// SetLogger lets you set your own logger.
func (em *EventMeter) SetLogger(l log.Logger) {
	em.logger = l
	em.wsc.SetLogger(l.With("module", "rpcclient"))
}

// String returns a string representation of event meter.
func (em *EventMeter) String() string {
	return em.wsc.Address
}

// Start boots up event meter.
func (em *EventMeter) Start() error {
	if err := em.wsc.Start(); err != nil {
		return err
	}

	em.quit = make(chan struct{})
	go em.receiveRoutine()
	go em.disconnectRoutine()

	err := em.subscribe()
	if err != nil {
		return err
	}
	em.subscribed = true
	return nil
}

// Stop stops event meter.
func (em *EventMeter) Stop() {
	close(em.quit)

	if em.wsc.IsRunning() {
		em.wsc.Stop()
	}
}

// Subscribe for the given query. Callback function will be called upon
// receiving an event.
func (em *EventMeter) Subscribe(query string, cb EventCallbackFunc) error {
	em.mtx.Lock()
	defer em.mtx.Unlock()

	if err := em.wsc.Subscribe(context.TODO(), query); err != nil {
		return err
	}

	metric := &EventMetric{
		meter:    metrics.NewMeter(),
		callback: cb,
	}
	em.queryToMetricMap[query] = metric
	return nil
}

// Unsubscribe from the given query.
func (em *EventMeter) Unsubscribe(query string) error {
	em.mtx.Lock()
	defer em.mtx.Unlock()
	if err := em.wsc.Unsubscribe(context.TODO(), query); err != nil {
		return err
	}

	return nil
}

// GetMetric fills in the latest data for an query and return a copy.
func (em *EventMeter) GetMetric(query string) (*EventMetric, error) {
	em.mtx.Lock()
	defer em.mtx.Unlock()
	metric, ok := em.queryToMetricMap[query]
	if !ok {
		return nil, fmt.Errorf("unknown query: %s", query)
	}
	return metric.fillMetric().Copy(), nil
}

// RegisterLatencyCallback allows you to set latency callback.
func (em *EventMeter) RegisterLatencyCallback(f LatencyCallbackFunc) {
	em.mtx.Lock()
	defer em.mtx.Unlock()
	em.latencyCallback = f
}

// RegisterDisconnectCallback allows you to set disconnect callback.
func (em *EventMeter) RegisterDisconnectCallback(f DisconnectCallbackFunc) {
	em.mtx.Lock()
	defer em.mtx.Unlock()
	em.disconnectCallback = f
}

///////////////////////////////////////////////////////////////////////////////
// Private

func (em *EventMeter) subscribe() error {
	for query, _ := range em.queryToMetricMap {
		if err := em.wsc.Subscribe(context.TODO(), query); err != nil {
			return err
		}
	}
	return nil
}

func (em *EventMeter) receiveRoutine() {
	latencyTicker := time.NewTicker(latencyPeriod)
	for {
		select {
		case resp := <-em.wsc.ResponsesCh:
			if resp.Error != nil {
				em.logger.Error("expected some event, got error", "err", resp.Error.Error())
				continue
			}
			query, data, err := em.unmarshalEvent(resp.Result)
			if err != nil {
				em.logger.Error("failed to unmarshal event", "err", err)
				continue
			}
			if query != "" { // FIXME how can it be an empty string?
				em.updateMetric(query, data)
			}
		case <-latencyTicker.C:
			if em.wsc.IsActive() {
				em.callLatencyCallback(em.wsc.PingPongLatencyTimer.Mean())
			}
		case <-em.wsc.Quit():
			return
		case <-em.quit:
			return
		}
	}
}

func (em *EventMeter) disconnectRoutine() {
	ticker := time.NewTicker(connectionCheckPeriod)
	for {
		select {
		case <-ticker.C:
			if em.wsc.IsReconnecting() && em.subscribed { // notify user about disconnect only once
				em.callDisconnectCallback()
				em.subscribed = false
			} else if !em.wsc.IsReconnecting() && !em.subscribed { // resubscribe
				em.subscribe()
				em.subscribed = true
			}
		case <-em.wsc.Quit():
			return
		case <-em.quit:
			return
		}
	}
}

func (em *EventMeter) updateMetric(query string, data events.EventData) {
	em.mtx.Lock()
	defer em.mtx.Unlock()

	metric, ok := em.queryToMetricMap[query]
	if !ok {
		// we already unsubscribed, or got an unexpected query
		return
	}

	last := metric.LastHeard
	metric.LastHeard = time.Now()
	metric.meter.Mark(1)
	dur := int64(metric.LastHeard.Sub(last))
	if dur < metric.MinDuration {
		metric.MinDuration = dur
	}
	if !last.IsZero() && dur > metric.MaxDuration {
		metric.MaxDuration = dur
	}

	if metric.callback != nil {
		go metric.callback(metric.Copy(), data)
	}
}

func (em *EventMeter) callDisconnectCallback() {
	em.mtx.Lock()
	if em.disconnectCallback != nil {
		go em.disconnectCallback()
	}
	em.mtx.Unlock()
}

func (em *EventMeter) callLatencyCallback(meanLatencyNanoSeconds float64) {
	em.mtx.Lock()
	if em.latencyCallback != nil {
		go em.latencyCallback(meanLatencyNanoSeconds)
	}
	em.mtx.Unlock()
}
<|endoftext|>


<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 expressions

import (
	"fmt"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/tidb/context"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/plan"
	"github.com/pingcap/tidb/stmt"
)

// SubQueryStatement implements stmt.Statement and plan.Planner interface.
type SubQueryStatement interface {
	stmt.Statement
	plan.Planner
}

var _ expression.Expression = (*SubQuery)(nil)

// SubQuery expresion holds a select statement.
// TODO: complete according to https://dev.mysql.com/doc/refman/5.7/en/subquery-restrictions.html
type SubQuery struct {
	// Stmt is the sub select statement.
	Stmt SubQueryStatement
	// Value holds the sub select result.
	Value interface{}

	// UseOuterQuery represents that whether subquery uses reference to a table for the outer query.
	// If use, we cannot cache the sub query result.
	UseOuterQuery bool

	p plan.Plan
}

// Clone implements the Expression Clone interface.
func (sq *SubQuery) Clone() (expression.Expression, error) {
	nsq := &SubQuery{Stmt: sq.Stmt, Value: sq.Value, p: sq.p, UseOuterQuery: sq.UseOuterQuery}
	return nsq, nil
}

// Eval implements the Expression Eval interface.
// Eval doesn't support multi rows return, so we can only get a scalar or a row result.
// If you want to get multi rows, use EvalRows instead.
func (sq *SubQuery) Eval(ctx context.Context, args map[interface{}]interface{}) (v interface{}, err error) {
	if !sq.UseOuterQuery && sq.Value != nil {
		return sq.Value, nil
	}

	rows, err := sq.EvalRows(ctx, args, 2)
	if err != nil {
		return nil, errors.Trace(err)
	}

	switch len(rows) {
	case 0:
		return nil, nil
	case 1:
		sq.Value = rows[0]
		return sq.Value, nil
	default:
		return nil, errors.Errorf("Subquery returns more than 1 row")
	}
}

// IsStatic implements the Expression IsStatic interface, always returns false.
func (sq *SubQuery) IsStatic() bool {
	return false
}

// String implements the Expression String interface.
func (sq *SubQuery) String() string {
	if sq.Stmt != nil {
		stmtStr := strings.TrimSuffix(sq.Stmt.OriginText(), ";")
		return fmt.Sprintf("(%s)", stmtStr)
	}
	return ""
}

// ColumnCount returns column count for the sub query.
func (sq *SubQuery) ColumnCount(ctx context.Context) (int, error) {
	p, err := sq.Plan(ctx)
	if err != nil {
		return 0, errors.Trace(err)
	}
	return len(p.GetFields()), nil
}

// Plan implements plan.Planner interface.
func (sq *SubQuery) Plan(ctx context.Context) (plan.Plan, error) {
	if sq.p != nil {
		return sq.p, nil
	}

	var err error
	sq.p, err = sq.Stmt.Plan(ctx)
	return sq.p, errors.Trace(err)
}

// EvalRows executes the subquery and returns the multi rows with rowCount.
// rowCount < 0 means no limit.
// if the ColumnCount is 1, we will return a column result like {1, 2, 3}
// otherwise, we will return a table result like {{1, 1}, {2, 2}}
func (sq *SubQuery) EvalRows(ctx context.Context, args map[interface{}]interface{}, rowCount int) ([]interface{}, error) {
	p, err := sq.Plan(ctx)
	if err != nil {
		return nil, errors.Trace(err)
	}
	defer p.Close()

	sq.push(ctx)

	var (
		row *plan.Row
		res = []interface{}{}
	)

	for rowCount != 0 {
		row, err = p.Next(ctx)
		if err != nil {
			break
		}
		if row == nil {
			break
		}
		if len(row.Data) == 1 {
			res = append(res, row.Data[0])
		} else {
			res = append(res, row.Data)
		}

		if rowCount > 0 {
			rowCount--
		}
	}

	err0 := sq.pop(ctx)
	if err0 != nil {
		return res, errors.Trace(err0)
	}

	return res, errors.Trace(err)
}

// A dummy type to avoid naming collision in context.
type subQueryStackKeyType int

// String defines a Stringer function for debugging and pretty printing.
func (k subQueryStackKeyType) String() string {
	return "sub query stack"
}

// subQueryStackKey holds the running sub query's stack.
const subQueryStackKey subQueryStackKeyType = 0

func (sq *SubQuery) push(ctx context.Context) {
	var st []*SubQuery
	v := ctx.Value(subQueryStackKey)
	if v == nil {
		st = []*SubQuery{}
	} else {
		// must ok
		st = v.([]*SubQuery)
	}

	st = append(st, sq)
	ctx.SetValue(subQueryStackKey, st)
}

func (sq *SubQuery) pop(ctx context.Context) error {
	v := ctx.Value(subQueryStackKey)
	if v == nil {
		return errors.Errorf("pop empty sub query stack")
	}

	st := v.([]*SubQuery)

	// can not empty
	n := len(st) - 1
	if st[n] != sq {
		return errors.Errorf("pop invalid top sub query in stack, want %v, but top is %v", sq, st[n])
	}

	st[n] = nil
	st = st[0:n]
	if len(st) == 0 {
		ctx.ClearValue(subQueryStackKey)
		return nil
	}

	ctx.SetValue(subQueryStackKey, st)
	return nil
}

// SetOuterQueryUsed is called when current running subquery uses outer query.
func SetOuterQueryUsed(ctx context.Context) {
	v := ctx.Value(subQueryStackKey)
	if v == nil {
		return
	}

	st := v.([]*SubQuery)

	// if current sub query uses outer query, the select result can not be cached,
	// at the same time, all the upper sub query must not cache the result too.
	for i := len(st) - 1; i >= 0; i-- {
		st[i].UseOuterQuery = true
	}
}
<commit_msg>expressions: Address 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 expressions

import (
	"fmt"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/tidb/context"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/plan"
	"github.com/pingcap/tidb/stmt"
)

// SubQueryStatement implements stmt.Statement and plan.Planner interface.
type SubQueryStatement interface {
	stmt.Statement
	plan.Planner
}

var _ expression.Expression = (*SubQuery)(nil)

// SubQuery expresion holds a select statement.
// TODO: complete according to https://dev.mysql.com/doc/refman/5.7/en/subquery-restrictions.html
type SubQuery struct {
	// Stmt is the sub select statement.
	Stmt SubQueryStatement
	// Value holds the sub select result.
	Value interface{}

	// UseOuterQuery represents that whether subquery uses reference to a table for the outer query.
	// If use, we cannot cache the sub query result.
	UseOuterQuery bool

	p plan.Plan
}

// Clone implements the Expression Clone interface.
func (sq *SubQuery) Clone() (expression.Expression, error) {
	nsq := &SubQuery{Stmt: sq.Stmt, Value: sq.Value, p: sq.p, UseOuterQuery: sq.UseOuterQuery}
	return nsq, nil
}

// Eval implements the Expression Eval interface.
// Eval doesn't support multi rows return, so we can only get a scalar or a row result.
// If you want to get multi rows, use EvalRows instead.
func (sq *SubQuery) Eval(ctx context.Context, args map[interface{}]interface{}) (v interface{}, err error) {
	if !sq.UseOuterQuery && sq.Value != nil {
		return sq.Value, nil
	}

	rows, err := sq.EvalRows(ctx, args, 2)
	if err != nil {
		return nil, errors.Trace(err)
	}

	switch len(rows) {
	case 0:
		return nil, nil
	case 1:
		sq.Value = rows[0]
		return sq.Value, nil
	default:
		return nil, errors.Errorf("Subquery returns more than 1 row")
	}
}

// IsStatic implements the Expression IsStatic interface, always returns false.
func (sq *SubQuery) IsStatic() bool {
	return false
}

// String implements the Expression String interface.
func (sq *SubQuery) String() string {
	if sq.Stmt != nil {
		stmtStr := strings.TrimSuffix(sq.Stmt.OriginText(), ";")
		return fmt.Sprintf("(%s)", stmtStr)
	}
	return ""
}

// ColumnCount returns column count for the sub query.
func (sq *SubQuery) ColumnCount(ctx context.Context) (int, error) {
	p, err := sq.Plan(ctx)
	if err != nil {
		return 0, errors.Trace(err)
	}
	return len(p.GetFields()), nil
}

// Plan implements plan.Planner interface.
func (sq *SubQuery) Plan(ctx context.Context) (plan.Plan, error) {
	if sq.p != nil {
		return sq.p, nil
	}

	var err error
	sq.p, err = sq.Stmt.Plan(ctx)
	return sq.p, errors.Trace(err)
}

// EvalRows executes the subquery and returns the multi rows with rowCount.
// rowCount < 0 means no limit.
// If the ColumnCount is 1, we will return a column result like {1, 2, 3},
// otherwise, we will return a table result like {{1, 1}, {2, 2}}.
func (sq *SubQuery) EvalRows(ctx context.Context, args map[interface{}]interface{}, rowCount int) ([]interface{}, error) {
	p, err := sq.Plan(ctx)
	if err != nil {
		return nil, errors.Trace(err)
	}
	defer p.Close()

	sq.push(ctx)

	var (
		row *plan.Row
		res = []interface{}{}
	)

	for rowCount != 0 {
		row, err = p.Next(ctx)
		if err != nil {
			break
		}
		if row == nil {
			break
		}
		if len(row.Data) == 1 {
			res = append(res, row.Data[0])
		} else {
			res = append(res, row.Data)
		}

		if rowCount > 0 {
			rowCount--
		}
	}

	err0 := sq.pop(ctx)
	if err0 != nil {
		return res, errors.Trace(err0)
	}

	return res, errors.Trace(err)
}

// A dummy type to avoid naming collision in context.
type subQueryStackKeyType int

// String defines a Stringer function for debugging and pretty printing.
func (k subQueryStackKeyType) String() string {
	return "sub query stack"
}

// subQueryStackKey holds the running sub query's stack.
const subQueryStackKey subQueryStackKeyType = 0

func (sq *SubQuery) push(ctx context.Context) {
	var st []*SubQuery
	v := ctx.Value(subQueryStackKey)
	if v == nil {
		st = []*SubQuery{}
	} else {
		// must ok
		st = v.([]*SubQuery)
	}

	st = append(st, sq)
	ctx.SetValue(subQueryStackKey, st)
}

func (sq *SubQuery) pop(ctx context.Context) error {
	v := ctx.Value(subQueryStackKey)
	if v == nil {
		return errors.Errorf("pop empty sub query stack")
	}

	st := v.([]*SubQuery)

	// can not empty
	n := len(st) - 1
	if st[n] != sq {
		return errors.Errorf("pop invalid top sub query in stack, want %v, but top is %v", sq, st[n])
	}

	st[n] = nil
	st = st[0:n]
	if len(st) == 0 {
		ctx.ClearValue(subQueryStackKey)
		return nil
	}

	ctx.SetValue(subQueryStackKey, st)
	return nil
}

// SetOuterQueryUsed is called when current running subquery uses outer query.
func SetOuterQueryUsed(ctx context.Context) {
	v := ctx.Value(subQueryStackKey)
	if v == nil {
		return
	}

	st := v.([]*SubQuery)

	// if current sub query uses outer query, the select result can not be cached,
	// at the same time, all the upper sub query must not cache the result too.
	for i := len(st) - 1; i >= 0; i-- {
		st[i].UseOuterQuery = true
	}
}
<|endoftext|>


<commit_before>// Package console provides GopherJS wrappers for the JavaScript
// console.
//
// Some functions support format specifiers. The following specifiers
// are supported:
//
//     %s – Formats the value as a string
//     %d – Formats the value as an integer
//     %i – Same as %d
//     %f – Formats the value as a floating point value.
//     %o – Formats the value as an expandable DOM element (as in the Elements panel).
//     %O – Formats the value as an expandable JavaScript object.
//     %c – Formats the output string according to CSS styles you provide.
//
// This package does not provide functions for the aliases
// console.debug and console.info – use Log instead.
//
// For a more detailed explanation of the APIs, see for example
// Google's documentation at
// https://developers.google.com/chrome-developer-tools/docs/console-api.
//
// Portions of this documentation are modifications based on work
// created and shared by Google and used according to terms described
// in the Creative Commons 3.0 Attribution License.
package console // import "honnef.co/go/js/console"

import (
	"bytes"

	"github.com/gopherjs/gopherjs/js"
)

var c = js.Global.Get("console")

// Assert writes msg to the console if b is false.
func Assert(b bool, msg js.Any) {
	c.Call("assert", b, msg)
}

// Clear clears the console.
func Clear() {
	c.Call("clear")
}

// Count writes the number of times that it has been invoked at the
// same line and with the same label.
func Count(label string) {
	c.Call("count", label)
}

// Dir prints a JavaScript representation of the specified object. If
// the object being logged is an HTML element, then the properties of
// its DOM representation are displayed.
func Dir(obj js.Any) {
	c.Call("dir", obj)
}

// DirXML Prints an XML representation of the specified object. For
// HTML elements, calling this method is equivalent to calling Log.
func DirXML(obj js.Any) {
	c.Call("dirxml", obj)
}

// Error is like Log but also includes a stack trace.
func Error(objs ...js.Any) {
	c.Call("error", objs...)
}

// Group starts a new logging group with an optional title.
//
// All console output that occurs after calling this function appears
// in the same visual group. Groups can be nested.
//
// The title will be generated according to the rules of the Log
// function.
func Group(objs ...js.Any) {
	c.Call("group", objs...)
}

// GroupCollapsed is like Group, except that the newly created
// group starts collapsed instead of open.
func GroupCollapsed(objs ...js.Any) {
	c.Call("groupCollapsed", objs...)
}

// GroupEnd closes the currently active logging group.
func GroupEnd() {
	c.Call("groupEnd")
}

// Log displays a message in the console. You pass one or more objects
// to this method, each of which are evaluated and concatenated into a
// space-delimited string. The first parameter you pass to Log may
// contain format specifiers.
func Log(objs ...js.Any) {
	c.Call("log", objs...)
}

// Profile starts a new CPU profile with an optional label.
func Profile(label js.Any) {
	c.Call("profile", label)
}

// ProfileEnd stops the currently running CPU profile, if any.
func ProfileEnd() {
	c.Call("profileEnd")
}

// Time starts a new timer with an associated label. Calling TimeEnd
// with the same label will stop the timer and print the elapsed time.
func Time(label js.Any) {
	c.Call("time", label)
}

// TimeEnd ends a timer that was started with Time and prints the
// elapsed time.
func TimeEnd(label js.Any) {
	c.Call("timeEnd", label)
}

// Timestamp adds an event to the timeline during a recording session.
func Timestamp(label js.Any) {
	c.Call("timeStamp", label)
}

// Trace prints a stack trace, starting from the point where Trace was
// called.
func Trace() {
	c.Call("trace")
}

// Warn is like Log but displays a different icon alongside the
// message.
func Warn(objs ...js.Any) {
	c.Call("warn", objs...)
}

// Writer implements an io.Writer on top of the JavaScript console.
// Writes will be buffered until a newline is encountered, which will
// cause flushing up to the newline.
type Writer struct {
	buf *bytes.Buffer
}

func (w *Writer) Write(buf []byte) (n int, err error) {
	if len(buf) == 0 {
		return 0, nil
	}

	for i := len(buf); i >= 0; i-- {
		if buf[i] == '\n' {
			w.buf.Write(buf[:i])
			Log(w.buf.String())
			w.buf.Reset()
			w.buf.Write(buf[i+1:])
			break
		}
	}

	return len(buf), nil
}

func (w *Writer) WriteString(s string) (n int, err error) {
	return w.Write([]byte(s))
}

// Flush will flush the current line to the console.
func (w *Writer) Flush() {
	w.Write([]byte{'\n'})
}

func New() *Writer {
	return &Writer{buf: new(bytes.Buffer)}
}
<commit_msg>revert js.Any changes<commit_after>// Package console provides GopherJS wrappers for the JavaScript
// console.
//
// Some functions support format specifiers. The following specifiers
// are supported:
//
//     %s – Formats the value as a string
//     %d – Formats the value as an integer
//     %i – Same as %d
//     %f – Formats the value as a floating point value.
//     %o – Formats the value as an expandable DOM element (as in the Elements panel).
//     %O – Formats the value as an expandable JavaScript object.
//     %c – Formats the output string according to CSS styles you provide.
//
// This package does not provide functions for the aliases
// console.debug and console.info – use Log instead.
//
// For a more detailed explanation of the APIs, see for example
// Google's documentation at
// https://developers.google.com/chrome-developer-tools/docs/console-api.
//
// Portions of this documentation are modifications based on work
// created and shared by Google and used according to terms described
// in the Creative Commons 3.0 Attribution License.
package console // import "honnef.co/go/js/console"

import (
	"bytes"

	"github.com/gopherjs/gopherjs/js"
)

var c = js.Global.Get("console")

// Assert writes msg to the console if b is false.
func Assert(b bool, msg interface{}) {
	c.Call("assert", b, msg)
}

// Clear clears the console.
func Clear() {
	c.Call("clear")
}

// Count writes the number of times that it has been invoked at the
// same line and with the same label.
func Count(label string) {
	c.Call("count", label)
}

// Dir prints a JavaScript representation of the specified object. If
// the object being logged is an HTML element, then the properties of
// its DOM representation are displayed.
func Dir(obj interface{}) {
	c.Call("dir", obj)
}

// DirXML Prints an XML representation of the specified object. For
// HTML elements, calling this method is equivalent to calling Log.
func DirXML(obj interface{}) {
	c.Call("dirxml", obj)
}

// Error is like Log but also includes a stack trace.
func Error(objs ...interface{}) {
	c.Call("error", objs...)
}

// Group starts a new logging group with an optional title.
//
// All console output that occurs after calling this function appears
// in the same visual group. Groups can be nested.
//
// The title will be generated according to the rules of the Log
// function.
func Group(objs ...interface{}) {
	c.Call("group", objs...)
}

// GroupCollapsed is like Group, except that the newly created
// group starts collapsed instead of open.
func GroupCollapsed(objs ...interface{}) {
	c.Call("groupCollapsed", objs...)
}

// GroupEnd closes the currently active logging group.
func GroupEnd() {
	c.Call("groupEnd")
}

// Log displays a message in the console. You pass one or more objects
// to this method, each of which are evaluated and concatenated into a
// space-delimited string. The first parameter you pass to Log may
// contain format specifiers.
func Log(objs ...interface{}) {
	c.Call("log", objs...)
}

// Profile starts a new CPU profile with an optional label.
func Profile(label interface{}) {
	c.Call("profile", label)
}

// ProfileEnd stops the currently running CPU profile, if any.
func ProfileEnd() {
	c.Call("profileEnd")
}

// Time starts a new timer with an associated label. Calling TimeEnd
// with the same label will stop the timer and print the elapsed time.
func Time(label interface{}) {
	c.Call("time", label)
}

// TimeEnd ends a timer that was started with Time and prints the
// elapsed time.
func TimeEnd(label interface{}) {
	c.Call("timeEnd", label)
}

// Timestamp adds an event to the timeline during a recording session.
func Timestamp(label interface{}) {
	c.Call("timeStamp", label)
}

// Trace prints a stack trace, starting from the point where Trace was
// called.
func Trace() {
	c.Call("trace")
}

// Warn is like Log but displays a different icon alongside the
// message.
func Warn(objs ...interface{}) {
	c.Call("warn", objs...)
}

// Writer implements an io.Writer on top of the JavaScript console.
// Writes will be buffered until a newline is encountered, which will
// cause flushing up to the newline.
type Writer struct {
	buf *bytes.Buffer
}

func (w *Writer) Write(buf []byte) (n int, err error) {
	if len(buf) == 0 {
		return 0, nil
	}

	for i := len(buf); i >= 0; i-- {
		if buf[i] == '\n' {
			w.buf.Write(buf[:i])
			Log(w.buf.String())
			w.buf.Reset()
			w.buf.Write(buf[i+1:])
			break
		}
	}

	return len(buf), nil
}

func (w *Writer) WriteString(s string) (n int, err error) {
	return w.Write([]byte(s))
}

// Flush will flush the current line to the console.
func (w *Writer) Flush() {
	w.Write([]byte{'\n'})
}

func New() *Writer {
	return &Writer{buf: new(bytes.Buffer)}
}
<|endoftext|>


<commit_before>package dataProcess

import (
	"../autils"
	"database/sql"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
	"time"
)

// 获取流量信息
func GetDFlow(c *gin.Context, db *sql.DB) {
	q, _ := c.Get("conditions")
	sDate, eDate := autils.AnaDate(q)
	vas, _ := time.Parse(shortForm, sDate)
	vae, _ := time.Parse(shortForm, eDate)

	dateList := dateCtt{}

	dn := autils.AnaSelect(q)

	if dn == "" {
		dn = "120ask.com"
	}

	if sDate != "" && eDate != "" && vae.After(vas) {
		t := vas
		s := autils.GetCurrentData(t)
		e := eDate
		for {
			if s != e {
				t = t.AddDate(0, 0, 1)
				s = autils.GetCurrentData(t)
				dateList = append(dateList, s)
			} else {
				break
			}
		}

	} else {
		var maxLenth int
		ml := c.Query("max")
		if ml != "" {
			maxLenth, _ = strconv.Atoi(ml)
		}

		if maxLenth == 0 {
			maxLenth = 15
		}
		now := time.Now()
		for i := -maxLenth; i < 0; i++ {
			t := now.AddDate(0, 0, i)
			dateList = append(dateList, autils.GetCurrentData(t))
		}
	}

	ls := flineStruct{}

	var click, display, tClick, tDisplay, cRate, fRate string

	rows, err := db.Query("select click, display, total_click, total_display, cd_rate, flow_rate from site_flow where date >= '" + dateList[0] + "' and  date <= '" + dateList[len(dateList)-1] + "' and domain = '" + dn + "'")

	autils.ErrHadle(err)

	lcs := fseriesType{}
	lcs.Name = "MIP点击流量"

	dps := fseriesType{}
	dps.Name = "MIP展现次数"

	rts := fseriesType{}
	rts.Name = "MIP点展比"

	ct := fseriesType{}
	ct.Name = "点击总流量"

	dt := fseriesType{}
	dt.Name = "展现总次数"

	fr := fseriesType{}
	fr.Name = "MIP流量占比"

	for rows.Next() {
		err := rows.Scan(&click, &display, &tClick, &tDisplay, &cRate, &fRate)
		autils.ErrHadle(err)

		lcs.Data = append(lcs.Data, click)
		dps.Data = append(dps.Data, display)
		rts.Data = append(rts.Data, cRate)
		ct.Data = append(ct.Data, tClick)
		dt.Data = append(dt.Data, tDisplay)
		fr.Data = append(fr.Data, fRate)
	}
	err = rows.Err()
	autils.ErrHadle(err)

	ls.Series = append(ls.Series, lcs, dps, rts, ct, dt, fr)
	ls.Categories = dateList

	defer rows.Close()

	c.JSON(http.StatusOK, gin.H{
		"status": 0,
		"msg":    "ok",
		"data":   ls,
	})
}
<commit_msg>fixed no data bug.<commit_after>package dataProcess

import (
	"../autils"
	"database/sql"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
	"time"
)

// 获取流量信息
func GetDFlow(c *gin.Context, db *sql.DB) {
	q, _ := c.Get("conditions")
	sDate, eDate := autils.AnaDate(q)
	vas, _ := time.Parse(shortForm, sDate)
	vae, _ := time.Parse(shortForm, eDate)

	dateList := dateCtt{}

	dn := autils.AnaSelect(q)

	if dn == "" {
		dn = "120ask.com"
	}

	if sDate != "" && eDate != "" && vae.After(vas) {
		t := vas
		s := autils.GetCurrentData(t)
		e := eDate
		for {
			if s != e {
				t = t.AddDate(0, 0, 1)
				s = autils.GetCurrentData(t)
				dateList = append(dateList, s)
			} else {
				break
			}
		}

	} else {
		var maxLenth int
		ml := c.Query("max")
		if ml != "" {
			maxLenth, _ = strconv.Atoi(ml)
		}

		if maxLenth == 0 {
			maxLenth = 15
		}
		now := time.Now()
		for i := -maxLenth; i < 0; i++ {
			t := now.AddDate(0, 0, i)
			dateList = append(dateList, autils.GetCurrentData(t))
		}
	}

	ls := flineStruct{}

	var click, display, tClick, tDisplay, cRate, fRate string

	rows, err := db.Query("select click, display, total_click, total_display, cd_rate, flow_rate from site_flow where date >= '" + dateList[0] + "' and  date <= '" + dateList[len(dateList)-1] + "' and domain = '" + dn + "'")

	autils.ErrHadle(err)

	lcs := fseriesType{}
	lcs.Name = "MIP点击流量"

	dps := fseriesType{}
	dps.Name = "MIP展现次数"

	rts := fseriesType{}
	rts.Name = "MIP点展比"

	ct := fseriesType{}
	ct.Name = "点击总流量"

	dt := fseriesType{}
	dt.Name = "展现总次数"

	fr := fseriesType{}
	fr.Name = "MIP流量占比"

	for rows.Next() {
		err := rows.Scan(&click, &display, &tClick, &tDisplay, &cRate, &fRate)
		autils.ErrHadle(err)

		lcs.Data = append(lcs.Data, click)
		dps.Data = append(dps.Data, display)
		rts.Data = append(rts.Data, cRate)
		ct.Data = append(ct.Data, tClick)
		dt.Data = append(dt.Data, tDisplay)
		fr.Data = append(fr.Data, fRate)
	}
	err = rows.Err()
	autils.ErrHadle(err)

	ls.Series = append(ls.Series, lcs, dps, rts, ct, dt, fr)
	ls.Categories = dateList

	defer rows.Close()

	if len(lcs.Data) == 0 || len(dps.Data) == 0 || len(rts.Data) == 0 || len(ct.Data) == 0 || len(dt.Data) == 0 || len(fr.Data) == 0 {
		c.JSON(http.StatusOK, gin.H{
			"status": -1,
			"msg":    "无数据",
			"data":   "",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"status": 0,
		"msg":    "ok",
		"data":   ls,
	})
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"regexp"
	"strings"

	"golang.org/x/net/html"

	"github.com/PuerkitoBio/goquery"
)

// addDivs adds kobo divs.
func addDivs(doc *goquery.Document) error {
	doc.Find("body>*").WrapAllHtml(`<div class="book-inner"></div>`)
	doc.Find("body>*").WrapAllHtml(`<div class="book-columns"></div>`)
	return nil
}

// addSpansToNode is a recursive helper function for addSpans.
func addSpansToNode(node *html.Node, paragraph *int, segment *int) {
	sentencere := regexp.MustCompile(`((?m).*?[\.\!\?\:]['"”’“…]?\s*)`)

	// Part 2 of hacky way of setting innerhtml of a textnode by double escaping everything, and deescaping once afterwards
	newAttr := []html.Attribute{}
	for _, a := range node.Attr {
		a.Key = html.EscapeString(a.Key)
		a.Val = html.EscapeString(a.Val)
		newAttr = append(newAttr, a)
	}
	node.Attr = newAttr

	if node.Type == html.TextNode {
		if node.Parent.Data == "pre" {
			// Do not add spans to pre elements
			return
		}
		*segment++

		sentencesindexes := sentencere.FindAllStringIndex(node.Data, -1)
		sentences := []string{}
		lasti := []int{0, 0}
		for _, i := range sentencesindexes {
			if lasti[1] != i[0] {
				// If gap in regex matches, add the gap to the sentence list to avoid losing text
				sentences = append(sentences, node.Data[lasti[1]:i[0]])
			}
			sentences = append(sentences, node.Data[i[0]:i[1]])
			lasti = i
		}
		if lasti[1] != len(node.Data) {
			// If gap in regex matches, add the gap to the sentence list to avoid losing text
			sentences = append(sentences, node.Data[lasti[1]:len(node.Data)])
		}

		var newhtml bytes.Buffer

		for _, sentence := range sentences {
			if strings.TrimSpace(sentence) != "" {
				newhtml.WriteString(fmt.Sprintf(`<span class="koboSpan" id="kobo.%v.%v">%s</span>`, *paragraph, *segment, sentence))
				*segment++
			}
		}

		// Part 1 of hacky way of setting innerhtml of a textnode by double escaping everything, and deescaping once afterwards
		node.Data = newhtml.String()

		return
	}
	if node.Type != html.ElementNode {
		return
	}
	if node.Data == "img" {
		return
	}
	if node.Data == "p" || node.Data == "ol" || node.Data == "ul" {
		*segment = 0
		*paragraph++
	}
	for c := node.FirstChild; c != nil; c = c.NextSibling {
		addSpansToNode(c, paragraph, segment)
	}
}

// addSpans adds kobo spans.
func addSpans(doc *goquery.Document) error {
	alreadyHasSpans := false
	doc.Find("span").Each(func(i int, s *goquery.Selection) {
		if val, _ := s.Attr("class"); strings.Contains(val, "koboSpan") {
			alreadyHasSpans = true
		}
	})
	if alreadyHasSpans {
		return nil
	}

	paragraph := 0
	segment := 0

	for _, n := range doc.Find("body").Nodes {
		addSpansToNode(n, &paragraph, &segment)
	}

	return nil
}

// openSelfClosingPs opens self-closing p tags.
func openSelfClosingPs(html *string) error {
	re := regexp.MustCompile(`<p[^>/]*/>`)
	*html = re.ReplaceAllString(*html, `<p></p>`)
	return nil
}

// smartenPunctuation smartens punctuation in html code. It must be run last.
func smartenPunctuation(html *string) error {
	// em and en dashes
	*html = strings.Replace(*html, "---", " – ", -1)
	*html = strings.Replace(*html, "--", " — ", -1)

	// TODO: smart quotes

	// Fix comments
	*html = strings.Replace(*html, "<! — ", "", -1)
	return nil
}

// cleanHTML cleans up html for a kobo epub.
func cleanHTML(html *string) error {
	emptyHeadingRe := regexp.MustCompile(`<h\d+>\s*</h\d+>`)
	*html = emptyHeadingRe.ReplaceAllString(*html, "")

	msPRe := regexp.MustCompile(`\s*<o:p>\s*<\/o:p>`)
	*html = msPRe.ReplaceAllString(*html, " ")

	msStRe := regexp.MustCompile(`<\/?st1:\w+>`)
	*html = msStRe.ReplaceAllString(*html, "")

	// unicode replacement chars
	*html = strings.Replace(*html, "�", "", -1)

	// ADEPT drm tags
	adeptRe := regexp.MustCompile(`(<meta\s+content=".+"\s+name="Adept.expected.resource"\s+\/>)`)
	*html = adeptRe.ReplaceAllString(*html, "")

	return nil
}

// process processes the html of a content file in an ordinary epub and converts it into a kobo epub by adding kobo divs, kobo spans, smartening punctuation, and cleaning html.
func process(content *string) error {
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(*content))
	if err != nil {
		return err
	}

	if err := addDivs(doc); err != nil {
		return err
	}

	if err := addSpans(doc); err != nil {
		return err
	}

	h, err := doc.Html()
	if err != nil {
		return err
	}

	// Part 3 of hacky way of setting innerhtml of a textnode by double escaping everything, and deescaping once afterwards. Must be done before further html processing
	h = html.UnescapeString(h)

	if err := openSelfClosingPs(&h); err != nil {
		return err
	}

	if err := cleanHTML(&h); err != nil {
		return err
	}

	if err := smartenPunctuation(&h); err != nil {
		return err
	}

	// Kobo style fixes
	h = strings.Replace(h, "", "", 1)

	*content = h

	return nil
}
<commit_msg>Add check to addDivs function<commit_after>package main

import (
	"bytes"
	"fmt"
	"regexp"
	"strings"

	"golang.org/x/net/html"

	"github.com/PuerkitoBio/goquery"
)

// addDivs adds kobo divs.
func addDivs(doc *goquery.Document) error {
	if len(doc.Find("div").Nodes) > len(doc.Find("p").Nodes) {
		// If there are more divs than ps, divs are probably being used as paragraphs, and adding the kobo divs will most likely break the book.
		return nil
	}
	doc.Find("body>*").WrapAllHtml(`<div class="book-inner"></div>`)
	doc.Find("body>*").WrapAllHtml(`<div class="book-columns"></div>`)
	return nil
}

// addSpansToNode is a recursive helper function for addSpans.
func addSpansToNode(node *html.Node, paragraph *int, segment *int) {
	sentencere := regexp.MustCompile(`((?m).*?[\.\!\?\:]['"”’“…]?\s*)`)

	// Part 2 of hacky way of setting innerhtml of a textnode by double escaping everything, and deescaping once afterwards
	newAttr := []html.Attribute{}
	for _, a := range node.Attr {
		a.Key = html.EscapeString(a.Key)
		a.Val = html.EscapeString(a.Val)
		newAttr = append(newAttr, a)
	}
	node.Attr = newAttr

	if node.Type == html.TextNode {
		if node.Parent.Data == "pre" {
			// Do not add spans to pre elements
			return
		}
		*segment++

		sentencesindexes := sentencere.FindAllStringIndex(node.Data, -1)
		sentences := []string{}
		lasti := []int{0, 0}
		for _, i := range sentencesindexes {
			if lasti[1] != i[0] {
				// If gap in regex matches, add the gap to the sentence list to avoid losing text
				sentences = append(sentences, node.Data[lasti[1]:i[0]])
			}
			sentences = append(sentences, node.Data[i[0]:i[1]])
			lasti = i
		}
		if lasti[1] != len(node.Data) {
			// If gap in regex matches, add the gap to the sentence list to avoid losing text
			sentences = append(sentences, node.Data[lasti[1]:len(node.Data)])
		}

		var newhtml bytes.Buffer

		for _, sentence := range sentences {
			if strings.TrimSpace(sentence) != "" {
				newhtml.WriteString(fmt.Sprintf(`<span class="koboSpan" id="kobo.%v.%v">%s</span>`, *paragraph, *segment, sentence))
				*segment++
			}
		}

		// Part 1 of hacky way of setting innerhtml of a textnode by double escaping everything, and deescaping once afterwards
		node.Data = newhtml.String()

		return
	}
	if node.Type != html.ElementNode {
		return
	}
	if node.Data == "img" {
		return
	}
	if node.Data == "p" || node.Data == "ol" || node.Data == "ul" {
		*segment = 0
		*paragraph++
	}
	for c := node.FirstChild; c != nil; c = c.NextSibling {
		addSpansToNode(c, paragraph, segment)
	}
}

// addSpans adds kobo spans.
func addSpans(doc *goquery.Document) error {
	alreadyHasSpans := false
	doc.Find("span").Each(func(i int, s *goquery.Selection) {
		if val, _ := s.Attr("class"); strings.Contains(val, "koboSpan") {
			alreadyHasSpans = true
		}
	})
	if alreadyHasSpans {
		return nil
	}

	paragraph := 0
	segment := 0

	for _, n := range doc.Find("body").Nodes {
		addSpansToNode(n, &paragraph, &segment)
	}

	return nil
}

// openSelfClosingPs opens self-closing p tags.
func openSelfClosingPs(html *string) error {
	re := regexp.MustCompile(`<p[^>/]*/>`)
	*html = re.ReplaceAllString(*html, `<p></p>`)
	return nil
}

// smartenPunctuation smartens punctuation in html code. It must be run last.
func smartenPunctuation(html *string) error {
	// em and en dashes
	*html = strings.Replace(*html, "---", " – ", -1)
	*html = strings.Replace(*html, "--", " — ", -1)

	// TODO: smart quotes

	// Fix comments
	*html = strings.Replace(*html, "<! — ", "", -1)
	return nil
}

// cleanHTML cleans up html for a kobo epub.
func cleanHTML(html *string) error {
	emptyHeadingRe := regexp.MustCompile(`<h\d+>\s*</h\d+>`)
	*html = emptyHeadingRe.ReplaceAllString(*html, "")

	msPRe := regexp.MustCompile(`\s*<o:p>\s*<\/o:p>`)
	*html = msPRe.ReplaceAllString(*html, " ")

	msStRe := regexp.MustCompile(`<\/?st1:\w+>`)
	*html = msStRe.ReplaceAllString(*html, "")

	// unicode replacement chars
	*html = strings.Replace(*html, "�", "", -1)

	// ADEPT drm tags
	adeptRe := regexp.MustCompile(`(<meta\s+content=".+"\s+name="Adept.expected.resource"\s+\/>)`)
	*html = adeptRe.ReplaceAllString(*html, "")

	return nil
}

// process processes the html of a content file in an ordinary epub and converts it into a kobo epub by adding kobo divs, kobo spans, smartening punctuation, and cleaning html.
func process(content *string) error {
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(*content))
	if err != nil {
		return err
	}

	if err := addDivs(doc); err != nil {
		return err
	}

	if err := addSpans(doc); err != nil {
		return err
	}

	h, err := doc.Html()
	if err != nil {
		return err
	}

	// Part 3 of hacky way of setting innerhtml of a textnode by double escaping everything, and deescaping once afterwards. Must be done before further html processing
	h = html.UnescapeString(h)

	if err := openSelfClosingPs(&h); err != nil {
		return err
	}

	if err := cleanHTML(&h); err != nil {
		return err
	}

	if err := smartenPunctuation(&h); err != nil {
		return err
	}

	// Kobo style fixes
	h = strings.Replace(h, "", "", 1)

	*content = h

	return nil
}
<|endoftext|>


<commit_before>package database

import (
	"sort"
	"testing"
)

func TestByVersion(t *testing.T) {
	migrations := []AppliedMigration{
		{Name: "v1", Version: 1},
		{Name: "v4", Version: 4},
		{Name: "v2", Version: 2},
		{Name: "v3", Version: 3},
	}

	sort.Sort(sort.Reverse(byVersion(migrations)))

	curVersion := len(migrations)

	for _, v := range migrations {
		if v.Version > curVersion {
			t.Error("Version should be less than the previous one")
		}

		curVersion = v.Version
	}
}
<commit_msg>Some tests<commit_after>package database

import (
	"sort"
	"testing"
)

type migrationOne struct{}
type migrationTwo struct{}
type migrationThree struct{}

func (m migrationOne) Name() string   { return "20170501_test1" }
func (m migrationTwo) Name() string   { return "20170502_test2" }
func (m migrationThree) Name() string { return "20170601_test3" }

func (m migrationOne) Up() string   { return "" }
func (m migrationTwo) Up() string   { return "" }
func (m migrationThree) Up() string { return "" }

func (m migrationOne) Down() string   { return "" }
func (m migrationTwo) Down() string   { return "" }
func (m migrationThree) Down() string { return "" }

func (m migrationOne) String() string   { return m.Name() }
func (m migrationTwo) String() string   { return m.Name() }
func (m migrationThree) String() string { return m.Name() }

func TestByVersion(t *testing.T) {
	migrations := []AppliedMigration{
		{Name: "v1", Version: 1},
		{Name: "v4", Version: 4},
		{Name: "v2", Version: 2},
		{Name: "v3", Version: 3},
	}

	sort.Sort(sort.Reverse(byVersion(migrations)))

	curVersion := len(migrations)

	for _, v := range migrations {
		if v.Version > curVersion {
			t.Error("Version should be less than the previous one")
		}

		curVersion = v.Version
	}
}

func TestByName(t *testing.T) {
	migrations := []Migration{
		migrationThree{},
		migrationTwo{},
		migrationOne{},
	}

	sort.Sort(byName(migrations))

	if migrations[0].Name() != "20170501_test1" {
		t.Error("Invalid migration")
	}

	if migrations[1].Name() != "20170502_test2" {
		t.Error("Invalid migration")
	}

	if migrations[2].Name() != "20170601_test3" {
		t.Error("Invalid migration")
	}
}
<|endoftext|>


<commit_before>package iot

import (
	. "github.com/influxdata/influxdb-comparisons/bulk_data_gen/common"
	"math/rand"
	"time"
)

var (
	CameraDetectionByteString = []byte("camera_detection") // heap optimization
)

var DetectionObjects = [][]byte{
	[]byte("animal"),
	[]byte("human"),
	[]byte("vehicle"),
	[]byte("unknown"),
}

var Animals = [][]byte{
	[]byte("cat"),
	[]byte("dog"),
	[]byte("bird"),
	[]byte("unknown"),
}

var Humans = [][]byte{
	[]byte("man"),
	[]byte("woman"),
	[]byte("child"),
	[]byte("unknown"),
}

var Vehicles = [][]byte{
	[]byte("car"),
	[]byte("lorry"),
	[]byte("truck"),
	[]byte("motorcycle"),
	[]byte("bicycle"),
	[]byte("unknown"),
}

var (
	// Field keys for 'air condition indoor' points.
	CameraDetectionFieldKeys = [][]byte{
		[]byte("object_type"),
		[]byte("object_kind"),
		[]byte("battery_voltage"),
	}
)

type CameraDetectionMeasurement struct {
	sensorId    []byte
	timestamp   time.Time
	batteryDist Distribution
	object      []byte
	kind        []byte
}

func NewCameraDetectionMeasurement(start time.Time, id []byte) *CameraDetectionMeasurement {

	//battery_voltage
	batteryDist := MUDWD(ND(0.01, 0.005), 1, 3.2, 3.2)

	return &CameraDetectionMeasurement{
		timestamp:   start,
		batteryDist: batteryDist,
		sensorId:    id,
	}
}

func (m *CameraDetectionMeasurement) Tick(d time.Duration) {
	m.timestamp = m.timestamp.Add(d)
	object := rand.Int63n(int64(len(DetectionObjects)))
	m.object = DetectionObjects[object]
	switch object {
	case 0: //animal
		m.kind = Animals[rand.Int63n(int64(len(Animals)))]
		break
	case 1: //human
		m.kind = Humans[rand.Int63n(int64(len(Humans)))]
		break
	case 2: //vehicle
		m.kind = Vehicles[rand.Int63n(int64(len(Vehicles)))]
		break
	case 3: //uknown
		m.kind = []byte("uknown")

	}
	m.batteryDist.Advance()
}

func (m *CameraDetectionMeasurement) ToPoint(p *Point) bool {
	p.SetMeasurementName(CameraDetectionByteString)
	p.SetTimestamp(&m.timestamp)
	p.AppendTag(SensorHomeTagKeys[0], m.sensorId)
	p.AppendField(CameraDetectionFieldKeys[0], m.object)
	p.AppendField(CameraDetectionFieldKeys[1], m.kind)
	p.AppendField(CameraDetectionFieldKeys[2], m.batteryDist.Get())
	return true
}
<commit_msg>Fixed initial empty camera_detection values<commit_after>package iot

import (
	. "github.com/influxdata/influxdb-comparisons/bulk_data_gen/common"
	"math/rand"
	"time"
)

var (
	CameraDetectionByteString = []byte("camera_detection") // heap optimization
)

var DetectionObjects = [][]byte{
	[]byte("animal"),
	[]byte("human"),
	[]byte("vehicle"),
	[]byte("unknown"),
}

var Animals = [][]byte{
	[]byte("cat"),
	[]byte("dog"),
	[]byte("bird"),
	[]byte("unknown"),
}

var Humans = [][]byte{
	[]byte("man"),
	[]byte("woman"),
	[]byte("child"),
	[]byte("unknown"),
}

var Vehicles = [][]byte{
	[]byte("car"),
	[]byte("lorry"),
	[]byte("truck"),
	[]byte("motorcycle"),
	[]byte("bicycle"),
	[]byte("unknown"),
}

var (
	// Field keys for 'air condition indoor' points.
	CameraDetectionFieldKeys = [][]byte{
		[]byte("object_type"),
		[]byte("object_kind"),
		[]byte("battery_voltage"),
	}
)

type CameraDetectionMeasurement struct {
	sensorId    []byte
	timestamp   time.Time
	batteryDist Distribution
	object      []byte
	kind        []byte
}

func NewCameraDetectionMeasurement(start time.Time, id []byte) *CameraDetectionMeasurement {

	//battery_voltage
	batteryDist := MUDWD(ND(0.01, 0.005), 1, 3.2, 3.2)

	cd := &CameraDetectionMeasurement{
		timestamp:   start,
		batteryDist: batteryDist,
		sensorId:    id,
	}
	cd.newDetection()
	return cd
}

func (m *CameraDetectionMeasurement) newDetection() {
	object := rand.Int63n(int64(len(DetectionObjects)))
	m.object = DetectionObjects[object]
	switch object {
	case 0: //animal
		m.kind = Animals[rand.Int63n(int64(len(Animals)))]
		break
	case 1: //human
		m.kind = Humans[rand.Int63n(int64(len(Humans)))]
		break
	case 2: //vehicle
		m.kind = Vehicles[rand.Int63n(int64(len(Vehicles)))]
		break
	case 3: //uknown
		m.kind = []byte("uknown")
	}
}

func (m *CameraDetectionMeasurement) Tick(d time.Duration) {
	m.timestamp = m.timestamp.Add(d)
	m.batteryDist.Advance()
	m.newDetection()
}

func (m *CameraDetectionMeasurement) ToPoint(p *Point) bool {
	p.SetMeasurementName(CameraDetectionByteString)
	p.SetTimestamp(&m.timestamp)
	p.AppendTag(SensorHomeTagKeys[0], m.sensorId)
	p.AppendField(CameraDetectionFieldKeys[0], m.object)
	p.AppendField(CameraDetectionFieldKeys[1], m.kind)
	p.AppendField(CameraDetectionFieldKeys[2], m.batteryDist.Get())
	return true
}
<|endoftext|>


<commit_before>package elicit

import (
	"fmt"
	"regexp"
	"strings"
	"testing"

	"bytes"
	"reflect"
)

// Context stores test machinery and maintains state between specs/scenarios/steps
type Context struct {
	specs           specCollection
	stepImpls       stepImplMap
	transforms      transformMap
	currentSpec     *SpecContext
	currentScenario *ScenarioContext
}

// StepArgumentTransform transforms captured groups in the step pattern to a function parameter type
// Note that if the actual string cannot be converted to the target type by the transform, it should return false
type StepArgumentTransform func(*Context, string, reflect.Type) (interface{}, bool)

type specCollection []specDef
type stepImplMap map[*regexp.Regexp]interface{}
type transformMap map[*regexp.Regexp]StepArgumentTransform

// New creates a new elicit context which stores specs, steps and transforms
func New() *Context {
	ctx := &Context{
		stepImpls:  make(map[*regexp.Regexp]interface{}),
		transforms: make(map[*regexp.Regexp]StepArgumentTransform),
	}

	// TODO discover these automatically
	ctx.registerTransform(`.*`, stringTransform)
	ctx.registerTransform(`-?\d+`, intTransform)
	ctx.registerTransform(`(?:.+,\s*)*.+`, commaSliceTransform)

	return ctx
}

// WithSpecsFolder recursively adds the path to the discovery path of specs
func (ctx *Context) WithSpecsFolder(path string) *Context {
	ctx.specs.parseSpecFolder(path)
	return ctx
}

// WithSteps registers steps from the supplied map of patterns to functions
func (ctx *Context) WithSteps(steps map[string]interface{}) *Context {
	for p, fn := range steps {
		ctx.registerStep(p, fn)
	}
	return ctx
}

func (ctx *Context) registerStep(pattern string, stepFunc interface{}) {

	pattern = strings.TrimSpace(pattern)
	pattern = ensureCompleteMatch(pattern)

	p, err := regexp.Compile(pattern)

	if err != nil {
		panic(fmt.Sprintf("compiling step regexp %q, %s", pattern, err))
	}

	// TODO(matt) check the pattern captures the correct number of parameters

	ctx.stepImpls[p] = stepFunc
}

func ensureCompleteMatch(pattern string) string {
	if !strings.HasPrefix(pattern, "^") {
		pattern = "^" + pattern
	}

	if !strings.HasSuffix(pattern, "$") {
		pattern = pattern + "$"
	}

	return pattern
}

// WithTransforms registers step argument transforms from the suppled map of patterns to functions
func (ctx *Context) WithTransforms(txs map[string]StepArgumentTransform) *Context {
	for p, fn := range txs {
		ctx.registerTransform(p, fn)
	}
	return ctx
}

func (ctx *Context) registerTransform(pattern string, transform StepArgumentTransform) {
	pattern = ensureCompleteMatch(pattern)

	p, err := regexp.Compile(pattern)

	if err != nil {
		panic(fmt.Sprintf("compiling transform regexp %q, %s", pattern, err))
	}

	ctx.transforms[p] = transform
}

// RunTests runs all the discovered specs as tests
func (ctx *Context) RunTests(t *testing.T) {
	for _, spec := range ctx.specs {
		ctx.runSpecTest(t, spec)
	}
}

func (ctx *Context) runSpecTest(t *testing.T, spec specDef) {
	ctx.currentSpec = &SpecContext{
		name: spec.Name,
	}

	t.Run(spec.Name, func(t *testing.T) {
		for _, scenario := range spec.Scenarios {
			ctx.runScenarioTest(t, scenario)
		}
	})
}

func (ctx *Context) runScenarioTest(t *testing.T, scenario scenarioDef) {
	ctx.currentScenario = &ScenarioContext{
		name:    scenario.Name,
		skipped: false,
		failed:  false,
		logbuf:  bytes.Buffer{},
	}
	ctx.logScenarioStart()

	t.Run(scenario.Name, func(t *testing.T) {
		for _, b := range scenario.Spec.BeforeSteps {
			ctx.runStep(t, b)
		}

		for _, s := range scenario.Steps {
			ctx.runStep(t, s)
		}

		for _, a := range scenario.Spec.AfterSteps {
			ctx.runStep(t, a)
		}

		log := string(ctx.currentScenario.logbuf.Bytes())
		if ctx.currentScenario.failed {
			t.Errorf(log)
		} else if ctx.currentScenario.skipped {
			t.Skipf(log)
		}
	})
}

func (ctx *Context) runStep(t *testing.T, step stepDef) {
	ctx.currentScenario.currentStep = step.Text

	defer func() {
		if r := recover(); r != nil {
			ctx.Failf("panic during step execution: %s", r)
		}
	}()

	for regex, fn := range ctx.stepImpls {
		f := reflect.ValueOf(fn)
		params := regex.FindStringSubmatch(step.Text)

		if in, ok := ctx.convertParams(f, params, step.Tables); ok {

			if !ctx.currentScenario.skipped && !ctx.currentScenario.failed {
				f.Call(in)
			} else {
				ctx.Skip("")
			}

			if !ctx.currentScenario.skipped && !ctx.currentScenario.failed {
				ctx.stepPassed()
			}

			return
		}
	}

	ctx.stepNotFound()
}

func (ctx *Context) convertParams(f reflect.Value, stringParams []string, tables []stringTable) ([]reflect.Value, bool) {

	if stringParams == nil {
		return nil, false
	}

	paramCount := f.Type().NumIn()
	tableParamCount := 0
	tableType := reflect.TypeOf((*Table)(nil)).Elem()

	for p := paramCount - 1; p >= 0; p-- {
		thisParam := f.Type().In(p)
		if thisParam == tableType {
			paramCount--
			tableParamCount++
		} else {
			break
		}
	}

	if len(stringParams) != paramCount || tableParamCount != len(tables) {
		return nil, false
	}

	c := make([]reflect.Value, len(stringParams))
	for i, param := range stringParams {
		if i == 0 {
			if f.Type().In(0) != reflect.TypeOf(ctx) {
				return nil, false
			}
			c[i] = reflect.ValueOf(ctx)
		} else {
			pt := f.Type().In(i)

			if t, ok := ctx.convertParam(param, pt); ok {
				c[i] = t
			} else {
				return nil, false
			}
		}
	}

	for _, t := range tables {
		c = append(c, reflect.ValueOf(makeTable(t)))
	}

	return c, true
}

func (ctx *Context) convertParam(s string, target reflect.Type) (reflect.Value, bool) {
	for regex, tx := range ctx.transforms {
		params := regex.FindStringSubmatch(s)
		if params == nil || len(params) != 1 {
			continue
		}

		f := reflect.ValueOf(tx)
		fTyp := f.Type()

		in := make([]reflect.Value, fTyp.NumIn())
		in[0] = reflect.ValueOf(ctx)
		in[1] = reflect.ValueOf(s)
		in[2] = reflect.ValueOf(target)

		out := f.Call(in)
		if out[1].Interface().(bool) {
			return reflect.ValueOf(out[0].Interface()), true
		}
	}

	return reflect.Value{}, false
}

// Skip skips the current step execution and all subsequent steps
func (ctx *Context) Skip(format string, args ...interface{}) {
	ctx.logStepResult("⤹", format, args...)
	ctx.currentScenario.skipped = true
}

func (ctx *Context) stepNotFound() {
	ctx.logStepResult("?", "")
	ctx.currentScenario.skipped = true
}

func (ctx *Context) stepPassed() {
	ctx.logStepResult("✓", "")
}

// Fail records test step failure
func (ctx *Context) Fail() {
	ctx.Failf("")
}

// Failf logs the supplied message and records test step failure
func (ctx *Context) Failf(format string, args ...interface{}) {
	ctx.logStepResult("✘", format, args...)
	ctx.currentScenario.failed = true
}

func (ctx *Context) logScenarioStart() {
	fmt.Fprintf(&ctx.currentScenario.logbuf, "\n\t%s\n", ctx.currentScenario.name)
}

func (ctx *Context) logStepResult(prefix, format string, args ...interface{}) {
	if len(format) > 0 {
		format = fmt.Sprintf("\t\t%s %s\t(%s)\n", prefix, ctx.currentScenario.currentStep, format)
		fmt.Fprintf(&ctx.currentScenario.logbuf, format, args...)
	} else {
		fmt.Fprintf(&ctx.currentScenario.logbuf, "\t\t%s %s\n", prefix, ctx.currentScenario.currentStep)
	}
}
<commit_msg>Use Spec Path Instead of Name<commit_after>package elicit

import (
	"fmt"
	"regexp"
	"strings"
	"testing"

	"bytes"
	"reflect"
)

// Context stores test machinery and maintains state between specs/scenarios/steps
type Context struct {
	specs           specCollection
	stepImpls       stepImplMap
	transforms      transformMap
	currentSpec     *SpecContext
	currentScenario *ScenarioContext
}

// StepArgumentTransform transforms captured groups in the step pattern to a function parameter type
// Note that if the actual string cannot be converted to the target type by the transform, it should return false
type StepArgumentTransform func(*Context, string, reflect.Type) (interface{}, bool)

type specCollection []specDef
type stepImplMap map[*regexp.Regexp]interface{}
type transformMap map[*regexp.Regexp]StepArgumentTransform

// New creates a new elicit context which stores specs, steps and transforms
func New() *Context {
	ctx := &Context{
		stepImpls:  make(map[*regexp.Regexp]interface{}),
		transforms: make(map[*regexp.Regexp]StepArgumentTransform),
	}

	// TODO discover these automatically
	ctx.registerTransform(`.*`, stringTransform)
	ctx.registerTransform(`-?\d+`, intTransform)
	ctx.registerTransform(`(?:.+,\s*)*.+`, commaSliceTransform)

	return ctx
}

// WithSpecsFolder recursively adds the path to the discovery path of specs
func (ctx *Context) WithSpecsFolder(path string) *Context {
	ctx.specs.parseSpecFolder(path)
	return ctx
}

// WithSteps registers steps from the supplied map of patterns to functions
func (ctx *Context) WithSteps(steps map[string]interface{}) *Context {
	for p, fn := range steps {
		ctx.registerStep(p, fn)
	}
	return ctx
}

func (ctx *Context) registerStep(pattern string, stepFunc interface{}) {

	pattern = strings.TrimSpace(pattern)
	pattern = ensureCompleteMatch(pattern)

	p, err := regexp.Compile(pattern)

	if err != nil {
		panic(fmt.Sprintf("compiling step regexp %q, %s", pattern, err))
	}

	// TODO(matt) check the pattern captures the correct number of parameters

	ctx.stepImpls[p] = stepFunc
}

func ensureCompleteMatch(pattern string) string {
	if !strings.HasPrefix(pattern, "^") {
		pattern = "^" + pattern
	}

	if !strings.HasSuffix(pattern, "$") {
		pattern = pattern + "$"
	}

	return pattern
}

// WithTransforms registers step argument transforms from the suppled map of patterns to functions
func (ctx *Context) WithTransforms(txs map[string]StepArgumentTransform) *Context {
	for p, fn := range txs {
		ctx.registerTransform(p, fn)
	}
	return ctx
}

func (ctx *Context) registerTransform(pattern string, transform StepArgumentTransform) {
	pattern = ensureCompleteMatch(pattern)

	p, err := regexp.Compile(pattern)

	if err != nil {
		panic(fmt.Sprintf("compiling transform regexp %q, %s", pattern, err))
	}

	ctx.transforms[p] = transform
}

// RunTests runs all the discovered specs as tests
func (ctx *Context) RunTests(t *testing.T) {
	for _, spec := range ctx.specs {
		ctx.runSpecTest(t, spec)
	}
}

func (ctx *Context) runSpecTest(t *testing.T, spec specDef) {
	ctx.currentSpec = &SpecContext{
		name: spec.Name,
	}

	t.Run(spec.Path, func(t *testing.T) {
		for _, scenario := range spec.Scenarios {
			ctx.runScenarioTest(t, scenario)
		}
	})
}

func (ctx *Context) runScenarioTest(t *testing.T, scenario scenarioDef) {
	ctx.currentScenario = &ScenarioContext{
		name:    scenario.Name,
		skipped: false,
		failed:  false,
		logbuf:  bytes.Buffer{},
	}
	ctx.logScenarioStart()

	t.Run(scenario.Name, func(t *testing.T) {
		for _, b := range scenario.Spec.BeforeSteps {
			ctx.runStep(t, b)
		}

		for _, s := range scenario.Steps {
			ctx.runStep(t, s)
		}

		for _, a := range scenario.Spec.AfterSteps {
			ctx.runStep(t, a)
		}

		log := string(ctx.currentScenario.logbuf.Bytes())
		if ctx.currentScenario.failed {
			t.Errorf(log)
		} else if ctx.currentScenario.skipped {
			t.Skipf(log)
		}
	})
}

func (ctx *Context) runStep(t *testing.T, step stepDef) {
	ctx.currentScenario.currentStep = step.Text

	defer func() {
		if r := recover(); r != nil {
			ctx.Failf("panic during step execution: %s", r)
		}
	}()

	for regex, fn := range ctx.stepImpls {
		f := reflect.ValueOf(fn)
		params := regex.FindStringSubmatch(step.Text)

		if in, ok := ctx.convertParams(f, params, step.Tables); ok {

			if !ctx.currentScenario.skipped && !ctx.currentScenario.failed {
				f.Call(in)
			} else {
				ctx.Skip("")
			}

			if !ctx.currentScenario.skipped && !ctx.currentScenario.failed {
				ctx.stepPassed()
			}

			return
		}
	}

	ctx.stepNotFound()
}

func (ctx *Context) convertParams(f reflect.Value, stringParams []string, tables []stringTable) ([]reflect.Value, bool) {

	if stringParams == nil {
		return nil, false
	}

	paramCount := f.Type().NumIn()
	tableParamCount := 0
	tableType := reflect.TypeOf((*Table)(nil)).Elem()

	for p := paramCount - 1; p >= 0; p-- {
		thisParam := f.Type().In(p)
		if thisParam == tableType {
			paramCount--
			tableParamCount++
		} else {
			break
		}
	}

	if len(stringParams) != paramCount || tableParamCount != len(tables) {
		return nil, false
	}

	c := make([]reflect.Value, len(stringParams))
	for i, param := range stringParams {
		if i == 0 {
			if f.Type().In(0) != reflect.TypeOf(ctx) {
				return nil, false
			}
			c[i] = reflect.ValueOf(ctx)
		} else {
			pt := f.Type().In(i)

			if t, ok := ctx.convertParam(param, pt); ok {
				c[i] = t
			} else {
				return nil, false
			}
		}
	}

	for _, t := range tables {
		c = append(c, reflect.ValueOf(makeTable(t)))
	}

	return c, true
}

func (ctx *Context) convertParam(s string, target reflect.Type) (reflect.Value, bool) {
	for regex, tx := range ctx.transforms {
		params := regex.FindStringSubmatch(s)
		if params == nil || len(params) != 1 {
			continue
		}

		f := reflect.ValueOf(tx)
		fTyp := f.Type()

		in := make([]reflect.Value, fTyp.NumIn())
		in[0] = reflect.ValueOf(ctx)
		in[1] = reflect.ValueOf(s)
		in[2] = reflect.ValueOf(target)

		out := f.Call(in)
		if out[1].Interface().(bool) {
			return reflect.ValueOf(out[0].Interface()), true
		}
	}

	return reflect.Value{}, false
}

// Skip skips the current step execution and all subsequent steps
func (ctx *Context) Skip(format string, args ...interface{}) {
	ctx.logStepResult("⤹", format, args...)
	ctx.currentScenario.skipped = true
}

func (ctx *Context) stepNotFound() {
	ctx.logStepResult("?", "")
	ctx.currentScenario.skipped = true
}

func (ctx *Context) stepPassed() {
	ctx.logStepResult("✓", "")
}

// Fail records test step failure
func (ctx *Context) Fail() {
	ctx.Failf("")
}

// Failf logs the supplied message and records test step failure
func (ctx *Context) Failf(format string, args ...interface{}) {
	ctx.logStepResult("✘", format, args...)
	ctx.currentScenario.failed = true
}

func (ctx *Context) logScenarioStart() {
	fmt.Fprintf(&ctx.currentScenario.logbuf, "\n\t%s\n", ctx.currentScenario.name)
}

func (ctx *Context) logStepResult(prefix, format string, args ...interface{}) {
	if len(format) > 0 {
		format = fmt.Sprintf("\t\t%s %s\t(%s)\n", prefix, ctx.currentScenario.currentStep, format)
		fmt.Fprintf(&ctx.currentScenario.logbuf, format, args...)
	} else {
		fmt.Fprintf(&ctx.currentScenario.logbuf, "\t\t%s %s\n", prefix, ctx.currentScenario.currentStep)
	}
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"

	"github.com/murlokswarm/log"
	"github.com/murlokswarm/markup"
	"github.com/murlokswarm/uid"
)

var (
	contexts = map[uid.ID]Contexter{}
)

// Contexter represents the support where a component can be mounted.
// eg a window.
type Contexter interface {
	// The ID of the context.
	ID() uid.ID

	// Mounts the component and renders it in the context.
	Mount(c markup.Componer)

	// Renders an element.
	Render(elem *markup.Element)

	// If applicable, return the position of the context.
	Position() (x float64, y float64)

	// If applicable, moves the context.
	Move(x float64, y float64)

	// If applicable, return the size of the context.
	Size() (width float64, height float64)

	// If applicable, resizes the context.
	Resize(width float64, height float64)

	// If applicable, set the icon targeted by path.
	SetIcon(path string)

	// Close the context.
	// Should call markup.Dismount on its root component.
	// Should call UnregisterContext on itself.
	// Should perform additional cleanup if required.
	Close()
}

// Context returns the context of c.
// c must be mounted.
func Context(c markup.Componer) (ctx Contexter, err error) {
	var root *markup.Element

	if root, err = markup.ComponentRoot(c); err != nil {
		return
	}

	ctx, err = ContextByID(root.ContextID)
	return
}

// ContextByID returns the context registered under id.
func ContextByID(id uid.ID) (ctx Contexter, err error) {
	var registered bool

	if ctx, registered = contexts[id]; !registered {
		err = fmt.Errorf("context %v is not registered or has been closed", id)
	}

	return
}

// RegisterContext registers c.
// Should be used only in a driver implementation.
func RegisterContext(c Contexter) {
	if len(c.ID()) == 0 {
		log.Panicf("context %T is invalid. ID must be set", c)
	}

	if _, registered := contexts[c.ID()]; registered {
		log.Panicf("context %T with id %v is already registered", c, c.ID())
	}

	contexts[c.ID()] = c
}

// UnregisterContext unregisters c.
// Should be used only in a driver implementation.
func UnregisterContext(c Contexter) {
	delete(contexts, c.ID())
}

// ZeroContext is a placeholder context.
// It's used as a replacement for non available or non implemented features.
//
// Use of methods from a ZeroContext doesn't do anything.
type ZeroContext struct {
	id          uid.ID
	placeholder string
	root        markup.Componer
}

// NewZeroContext creates a ZeroContext.
func NewZeroContext(placeholder string) (ctx *ZeroContext) {
	ctx = &ZeroContext{
		id:          uid.Context(),
		placeholder: placeholder,
	}

	RegisterContext(ctx)
	return
}

// ID returns the ID of the context.
func (c *ZeroContext) ID() uid.ID {
	return c.id
}

// Mount is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) Mount(component markup.Componer) {
	markup.Mount(component, c.ID())
	log.Infof("%T is mounted into %v (%v)", component, c.placeholder, c.ID())
}

// Render is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) Render(elem *markup.Element) {
	log.Infof("rendering:\n\033[32m%v\033[00m", elem.HTML())
}

// Size is a placeholder method to satisfy the Contexter interface.
func (c *ZeroContext) Size() (width float64, height float64) {
	return
}

// Resize is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) Resize(width float64, height float64) {
	log.Infof("%v (%v) simulates a resize of %v x %v", c.placeholder, c.ID(), width, height)
}

// Position is a placeholder method to satisfy the Contexter interface.
func (c *ZeroContext) Position() (x float64, y float64) {
	return
}

// Move is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) Move(x float64, y float64) {
	log.Infof("%v (%v) simulates a move to (%v, %v)", c.placeholder, c.ID(), x, y)
}

// SetIcon is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) SetIcon(path string) {
	log.Infof("%v (%v) simulates set icon with %v", c.placeholder, c.ID(), path)
}

// Close is a closes the context.
func (c *ZeroContext) Close() {
	markup.Dismount(c.root)
	UnregisterContext(c)
	log.Infof("%v (%v) is closed", c.placeholder, c.ID())
}
<commit_msg>doc update<commit_after>package app

import (
	"fmt"

	"github.com/murlokswarm/log"
	"github.com/murlokswarm/markup"
	"github.com/murlokswarm/uid"
)

var (
	contexts = map[uid.ID]Contexter{}
)

// Contexter represents the support where a component can be mounted.
// eg a window.
type Contexter interface {
	// The ID of the context.
	ID() uid.ID

	// Mounts the component and renders it in the context.
	Mount(c markup.Componer)

	// Renders an element.
	Render(elem *markup.Element)

	// If applicable, returns the position of the context.
	Position() (x float64, y float64)

	// If applicable, moves the context.
	Move(x float64, y float64)

	// If applicable, returns the size of the context.
	Size() (width float64, height float64)

	// If applicable, resizes the context.
	Resize(width float64, height float64)

	// If applicable, set the icon targeted by path.
	SetIcon(path string)

	// If applicablex, closes the context.
	Close()
}

// Context returns the context of c.
// c must be mounted.
func Context(c markup.Componer) (ctx Contexter, err error) {
	var root *markup.Element

	if root, err = markup.ComponentRoot(c); err != nil {
		return
	}

	ctx, err = ContextByID(root.ContextID)
	return
}

// ContextByID returns the context registered under id.
func ContextByID(id uid.ID) (ctx Contexter, err error) {
	var registered bool

	if ctx, registered = contexts[id]; !registered {
		err = fmt.Errorf("context %v is not registered or has been closed", id)
	}

	return
}

// RegisterContext registers c.
// Should be used only in a driver implementation.
func RegisterContext(c Contexter) {
	if len(c.ID()) == 0 {
		log.Panicf("context %T is invalid. ID must be set", c)
	}

	if _, registered := contexts[c.ID()]; registered {
		log.Panicf("context %T with id %v is already registered", c, c.ID())
	}

	contexts[c.ID()] = c
}

// UnregisterContext unregisters c.
// Should be used only in a driver implementation.
func UnregisterContext(c Contexter) {
	delete(contexts, c.ID())
}

// ZeroContext is a placeholder context.
// It's used as a replacement for non available or non implemented features.
//
// Use of methods from a ZeroContext doesn't do anything.
type ZeroContext struct {
	id          uid.ID
	placeholder string
	root        markup.Componer
}

// NewZeroContext creates a ZeroContext.
func NewZeroContext(placeholder string) (ctx *ZeroContext) {
	ctx = &ZeroContext{
		id:          uid.Context(),
		placeholder: placeholder,
	}

	RegisterContext(ctx)
	return
}

// ID returns the ID of the context.
func (c *ZeroContext) ID() uid.ID {
	return c.id
}

// Mount is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) Mount(component markup.Componer) {
	markup.Mount(component, c.ID())
	log.Infof("%T is mounted into %v (%v)", component, c.placeholder, c.ID())
}

// Render is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) Render(elem *markup.Element) {
	log.Infof("rendering:\n\033[32m%v\033[00m", elem.HTML())
}

// Size is a placeholder method to satisfy the Contexter interface.
func (c *ZeroContext) Size() (width float64, height float64) {
	return
}

// Resize is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) Resize(width float64, height float64) {
	log.Infof("%v (%v) simulates a resize of %v x %v", c.placeholder, c.ID(), width, height)
}

// Position is a placeholder method to satisfy the Contexter interface.
func (c *ZeroContext) Position() (x float64, y float64) {
	return
}

// Move is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) Move(x float64, y float64) {
	log.Infof("%v (%v) simulates a move to (%v, %v)", c.placeholder, c.ID(), x, y)
}

// SetIcon is a placeholder method to satisfy the Contexter interface.
// It does nothing.
func (c *ZeroContext) SetIcon(path string) {
	log.Infof("%v (%v) simulates set icon with %v", c.placeholder, c.ID(), path)
}

// Close is a closes the context.
func (c *ZeroContext) Close() {
	markup.Dismount(c.root)
	UnregisterContext(c)
	log.Infof("%v (%v) is closed", c.placeholder, c.ID())
}
<|endoftext|>