<commit_before>package web

import (
	"encoding/gob"
	"encoding/json"
	"encoding/xml"
	"errors"
	"net/http"
	"net/url"
	"strings"
)

// createContext return a web.Context
func createContext(w http.ResponseWriter, r *http.Request, params *Params) *Context {

	ctx := &Context{
		W:     w,
		R:     r,
		param: params,
		code:  200,
	}

	return ctx
}

// Context is type of an web.Context
type Context struct {
	W http.ResponseWriter
	R *http.Request

	param       *Params
	query       *url.Values
	form        *url.Values
	userID      uint64
	accept      *string
	contentType *string
	code        int
}

// Init init context
func (ctx *Context) Init(userID uint64) {
	ctx.userID = userID
}

// UserID get userID
func (ctx *Context) UserID() uint64 {
	return ctx.userID
}

// Param get value from Params
func (ctx *Context) Param(name string) string {
	return ctx.param.Val(name)
}

// Query get value from QueryString
func (ctx *Context) Query(name string) string {
	if ctx.query == nil {
		query := ctx.R.URL.Query()
		ctx.query = &query
	}
	return ctx.query.Get(name)
}

// Form get value from Form
func (ctx *Context) Form(name string) string {
	if ctx.form == nil {
		ctx.form, _ = parseForm(ctx.R.Body)
	}
	return ctx.form.Get(name)
}

// Host return ctx.r.Host
func (ctx *Context) Host() string {
	return ctx.R.Host
}

// Path return ctx.r.URL.Path
func (ctx *Context) Path() string {
	return ctx.R.URL.Path
}

// Method return ctx.r.Method
func (ctx *Context) Method() string {
	return ctx.R.Method
}

// RemoteAddr return remote ip address
func (ctx *Context) RemoteAddr() string {
	return ctx.R.RemoteAddr
}

// UserAgent return User-Agent header
func (ctx *Context) UserAgent() string {
	return ctx.Get("User-Agent")
}

// IsAjax if X-Requested-With header is XMLHttpRequest return true, else false
func (ctx *Context) IsAjax() bool {
	return ctx.Get("X-Requested-With") == "XMLHttpRequest"
}

// TryParseBody decode val from Request.Body
func (ctx *Context) TryParseBody(val interface{}) error {
	switch {
	case strings.HasPrefix(ctx.ContentType(), "application/json"):
		return json.NewDecoder(ctx.R.Body).Decode(val)
	case strings.HasPrefix(ctx.ContentType(), "application/x-gob"):
		return gob.NewDecoder(ctx.R.Body).Decode(val)
	case strings.HasPrefix(ctx.ContentType(), "application/x-www-form-urlencoded"):
		return formReader(ctx, val)
	case strings.HasPrefix(ctx.ContentType(), "multipart/form-data"):
		return formDataReader(ctx, val)
	case strings.HasPrefix(ctx.ContentType(), "application/octet-stream"):
		return binaryReader(ctx, val)
	case strings.HasPrefix(ctx.ContentType(), "application/xml"):
		return xml.NewDecoder(ctx.R.Body).Decode(val)
	default:
		return errors.New("tryParseBody(unsupported contentType '" + ctx.ContentType() + "')")
	}
}

// TryParseParam decode val from Query
func (ctx *Context) TryParseParam(name string, val interface{}) error {
	return TryParse(ctx.Param(name), val)
}

// TryParseQuery decode val from Query
func (ctx *Context) TryParseQuery(name string, val interface{}) error {
	return TryParse(ctx.Query(name), val)
}

// TryParseForm decode val from Form
func (ctx *Context) TryParseForm(name string, val interface{}) error {
	return TryParse(ctx.Form(name), val)
}

// Write Write data base on accept header
func (ctx *Context) Write(val interface{}) error {
	switch ctx.Accept() {
	case "application/json":
		return ctx.WriteJSON(val)
	case "application/x-gob":
		return ctx.WriteGOB(val)
	case "application/xml":
		return ctx.WriteXML(val)
	case "application/octet-stream":
		return ctx.WriteBinary(val)
	default:
		return errors.New("write(unsupported accept '" + ctx.Accept() + "')")
	}
}

// WriteJSON Write JSON
func (ctx *Context) WriteJSON(val interface{}) error {
	ctx.W.WriteHeader(ctx.code)
	return json.NewEncoder(ctx.W).Encode(val)
}

// WriteXML Write XML
func (ctx *Context) WriteXML(val interface{}) error {
	ctx.W.WriteHeader(ctx.code)
	return xml.NewEncoder(ctx.W).Encode(val)
}

// WriteGOB Write GOB
func (ctx *Context) WriteGOB(val interface{}) error {
	ctx.W.WriteHeader(ctx.code)
	return gob.NewEncoder(ctx.W).Encode(val)
}

// WriteBinary Write Binary
func (ctx *Context) WriteBinary(val interface{}) error {
	return binaryWriter(ctx, val)
}

// Status return status code
func (ctx *Context) Status() int {
	return ctx.code
}

// SetStatus Write status code to header
func (ctx *Context) SetStatus(code int) {
	ctx.code = code
}

// Get get header, short hand for ctx.Request.Header.Get
func (ctx *Context) Get(key string) string {
	return ctx.R.Header.Get(key)
}

// Set set header, short hand for ctx.ResponseWriter.Header().Set
func (ctx *Context) Set(key string, value string) {
	ctx.W.Header().Set(key, value)
}

// Add add header, short hand for ctx.ResponseWriter.Header().Add
func (ctx *Context) Add(key string, value string) {
	ctx.W.Header().Add(key, value)
}

// Del del header, short hand for ctx.ResponseWriter.Header().Del
func (ctx *Context) Del(key string) {
	ctx.W.Header().Del(key)
}

// Accept get Accept from header
func (ctx *Context) Accept() string {
	if ctx.accept == nil {
		ac := ctx.Get("Accept")
		ctx.accept = &ac
	}
	return *ctx.accept
}

// ContentType get Content-Type from header
func (ctx *Context) ContentType() string {
	if ctx.contentType == nil {
		ctype := ctx.Get("Content-Type")
		ctx.contentType = &ctype
	}
	return *ctx.contentType
}

// SetContentType Set Content-Type to header
func (ctx *Context) SetContentType(val string) {
	ctx.Set("Content-Type", contentType(val))
}

// Redirect to url with status code
func (ctx *Context) Redirect(code int, url string) {
	ctx.Set("Location", url)
	ctx.SetStatus(code)
	ctx.W.WriteHeader(code)
}
<commit_msg>default WriteJSON<commit_after>package web

import (
	"encoding/gob"
	"encoding/json"
	"encoding/xml"
	"errors"
	"net/http"
	"net/url"
	"strings"
)

// createContext return a web.Context
func createContext(w http.ResponseWriter, r *http.Request, params *Params) *Context {

	ctx := &Context{
		W:     w,
		R:     r,
		param: params,
		code:  200,
	}

	return ctx
}

// Context is type of an web.Context
type Context struct {
	W http.ResponseWriter
	R *http.Request

	param       *Params
	query       *url.Values
	form        *url.Values
	userID      uint64
	accept      *string
	contentType *string
	code        int
}

// Init init context
func (ctx *Context) Init(userID uint64) {
	ctx.userID = userID
}

// UserID get userID
func (ctx *Context) UserID() uint64 {
	return ctx.userID
}

// Param get value from Params
func (ctx *Context) Param(name string) string {
	return ctx.param.Val(name)
}

// Query get value from QueryString
func (ctx *Context) Query(name string) string {
	if ctx.query == nil {
		query := ctx.R.URL.Query()
		ctx.query = &query
	}
	return ctx.query.Get(name)
}

// Form get value from Form
func (ctx *Context) Form(name string) string {
	if ctx.form == nil {
		ctx.form, _ = parseForm(ctx.R.Body)
	}
	return ctx.form.Get(name)
}

// Host return ctx.r.Host
func (ctx *Context) Host() string {
	return ctx.R.Host
}

// Path return ctx.r.URL.Path
func (ctx *Context) Path() string {
	return ctx.R.URL.Path
}

// Method return ctx.r.Method
func (ctx *Context) Method() string {
	return ctx.R.Method
}

// RemoteAddr return remote ip address
func (ctx *Context) RemoteAddr() string {
	return ctx.R.RemoteAddr
}

// UserAgent return User-Agent header
func (ctx *Context) UserAgent() string {
	return ctx.Get("User-Agent")
}

// IsAjax if X-Requested-With header is XMLHttpRequest return true, else false
func (ctx *Context) IsAjax() bool {
	return ctx.Get("X-Requested-With") == "XMLHttpRequest"
}

// TryParseBody decode val from Request.Body
func (ctx *Context) TryParseBody(val interface{}) error {
	switch {
	case strings.HasPrefix(ctx.ContentType(), "application/json"):
		return json.NewDecoder(ctx.R.Body).Decode(val)
	case strings.HasPrefix(ctx.ContentType(), "application/x-gob"):
		return gob.NewDecoder(ctx.R.Body).Decode(val)
	case strings.HasPrefix(ctx.ContentType(), "application/x-www-form-urlencoded"):
		return formReader(ctx, val)
	case strings.HasPrefix(ctx.ContentType(), "multipart/form-data"):
		return formDataReader(ctx, val)
	case strings.HasPrefix(ctx.ContentType(), "application/octet-stream"):
		return binaryReader(ctx, val)
	case strings.HasPrefix(ctx.ContentType(), "application/xml"):
		return xml.NewDecoder(ctx.R.Body).Decode(val)
	default:
		return errors.New("tryParseBody(unsupported contentType '" + ctx.ContentType() + "')")
	}
}

// TryParseParam decode val from Query
func (ctx *Context) TryParseParam(name string, val interface{}) error {
	return TryParse(ctx.Param(name), val)
}

// TryParseQuery decode val from Query
func (ctx *Context) TryParseQuery(name string, val interface{}) error {
	return TryParse(ctx.Query(name), val)
}

// TryParseForm decode val from Form
func (ctx *Context) TryParseForm(name string, val interface{}) error {
	return TryParse(ctx.Form(name), val)
}

// Write Write data base on accept header
func (ctx *Context) Write(val interface{}) error {
	switch ctx.Accept() {
	case "application/json":
		return ctx.WriteJSON(val)
	case "application/x-gob":
		return ctx.WriteGOB(val)
	case "application/xml":
		return ctx.WriteXML(val)
	case "application/octet-stream":
		return ctx.WriteBinary(val)
	default:
		return ctx.WriteJSON(val)
	}
}

// WriteJSON Write JSON
func (ctx *Context) WriteJSON(val interface{}) error {
	ctx.W.WriteHeader(ctx.code)
	return json.NewEncoder(ctx.W).Encode(val)
}

// WriteXML Write XML
func (ctx *Context) WriteXML(val interface{}) error {
	ctx.W.WriteHeader(ctx.code)
	return xml.NewEncoder(ctx.W).Encode(val)
}

// WriteGOB Write GOB
func (ctx *Context) WriteGOB(val interface{}) error {
	ctx.W.WriteHeader(ctx.code)
	return gob.NewEncoder(ctx.W).Encode(val)
}

// WriteBinary Write Binary
func (ctx *Context) WriteBinary(val interface{}) error {
	return binaryWriter(ctx, val)
}

// Status return status code
func (ctx *Context) Status() int {
	return ctx.code
}

// SetStatus Write status code to header
func (ctx *Context) SetStatus(code int) {
	ctx.code = code
}

// Get get header, short hand for ctx.Request.Header.Get
func (ctx *Context) Get(key string) string {
	return ctx.R.Header.Get(key)
}

// Set set header, short hand for ctx.ResponseWriter.Header().Set
func (ctx *Context) Set(key string, value string) {
	ctx.W.Header().Set(key, value)
}

// Add add header, short hand for ctx.ResponseWriter.Header().Add
func (ctx *Context) Add(key string, value string) {
	ctx.W.Header().Add(key, value)
}

// Del del header, short hand for ctx.ResponseWriter.Header().Del
func (ctx *Context) Del(key string) {
	ctx.W.Header().Del(key)
}

// Accept get Accept from header
func (ctx *Context) Accept() string {
	if ctx.accept == nil {
		ac := ctx.Get("Accept")
		ctx.accept = &ac
	}
	return *ctx.accept
}

// ContentType get Content-Type from header
func (ctx *Context) ContentType() string {
	if ctx.contentType == nil {
		ctype := ctx.Get("Content-Type")
		ctx.contentType = &ctype
	}
	return *ctx.contentType
}

// SetContentType Set Content-Type to header
func (ctx *Context) SetContentType(val string) {
	ctx.Set("Content-Type", contentType(val))
}

// Redirect to url with status code
func (ctx *Context) Redirect(code int, url string) {
	ctx.Set("Location", url)
	ctx.SetStatus(code)
	ctx.W.WriteHeader(code)
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"os"
	"runtime"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/gopasspw/gopass/pkg/backend/crypto/gpg"
	"github.com/gopasspw/gopass/pkg/config"
	"github.com/gopasspw/gopass/pkg/ctxutil"
	"github.com/gopasspw/gopass/pkg/store/sub"

	"github.com/fatih/color"
)

func initContext(ctx context.Context, cfg *config.Config) context.Context {
	// always trust
	ctx = gpg.WithAlwaysTrust(ctx, true)

	// check recipients conflicts with always trust, make sure it's not enabled
	// when always trust is
	if gpg.IsAlwaysTrust(ctx) {
		ctx = sub.WithCheckRecipients(ctx, false)
	}

	// debug flag
	if gdb := os.Getenv("GOPASS_DEBUG"); gdb == "true" {
		ctx = ctxutil.WithDebug(ctx, true)
	}

	// need this override for our integration tests
	if nc := os.Getenv("GOPASS_NOCOLOR"); nc == "true" || ctxutil.IsNoColor(ctx) {
		color.NoColor = true
		ctx = ctxutil.WithColor(ctx, false)
	}

	// support for no-color.org
	if nc := os.Getenv("NO_COLOR"); nc != "" {
		color.NoColor = true
		ctx = ctxutil.WithColor(ctx, false)
	}

	// only emit color codes when stdout is a terminal
	if !terminal.IsTerminal(int(os.Stdout.Fd())) {
		color.NoColor = true
		ctx = ctxutil.WithColor(ctx, false)
		ctx = ctxutil.WithTerminal(ctx, false)
		ctx = ctxutil.WithInteractive(ctx, false)
	}

	// reading from stdin?
	if info, err := os.Stdin.Stat(); err == nil && info.Mode()&os.ModeCharDevice == 0 {
		ctx = ctxutil.WithInteractive(ctx, false)
		ctx = ctxutil.WithStdin(ctx, true)
	}

	// disable colored output on windows since cmd.exe doesn't support ANSI color
	// codes. Other terminal may do, but until we can figure that out better
	// disable this for all terms on this platform
	if runtime.GOOS == "windows" {
		color.NoColor = true
		ctx = ctxutil.WithColor(ctx, false)
	}

	return ctx
}
<commit_msg>Ensure GOPASS_DEBUG works as documented (#1058)<commit_after>package main

import (
	"context"
	"os"
	"runtime"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/gopasspw/gopass/pkg/backend/crypto/gpg"
	"github.com/gopasspw/gopass/pkg/config"
	"github.com/gopasspw/gopass/pkg/ctxutil"
	"github.com/gopasspw/gopass/pkg/store/sub"

	"github.com/fatih/color"
)

func initContext(ctx context.Context, cfg *config.Config) context.Context {
	// always trust
	ctx = gpg.WithAlwaysTrust(ctx, true)

	// check recipients conflicts with always trust, make sure it's not enabled
	// when always trust is
	if gpg.IsAlwaysTrust(ctx) {
		ctx = sub.WithCheckRecipients(ctx, false)
	}

	// debug flag
	if gdb := os.Getenv("GOPASS_DEBUG"); gdb != "" {
		ctx = ctxutil.WithDebug(ctx, true)
	}

	// need this override for our integration tests
	if nc := os.Getenv("GOPASS_NOCOLOR"); nc == "true" || ctxutil.IsNoColor(ctx) {
		color.NoColor = true
		ctx = ctxutil.WithColor(ctx, false)
	}

	// support for no-color.org
	if nc := os.Getenv("NO_COLOR"); nc != "" {
		color.NoColor = true
		ctx = ctxutil.WithColor(ctx, false)
	}

	// only emit color codes when stdout is a terminal
	if !terminal.IsTerminal(int(os.Stdout.Fd())) {
		color.NoColor = true
		ctx = ctxutil.WithColor(ctx, false)
		ctx = ctxutil.WithTerminal(ctx, false)
		ctx = ctxutil.WithInteractive(ctx, false)
	}

	// reading from stdin?
	if info, err := os.Stdin.Stat(); err == nil && info.Mode()&os.ModeCharDevice == 0 {
		ctx = ctxutil.WithInteractive(ctx, false)
		ctx = ctxutil.WithStdin(ctx, true)
	}

	// disable colored output on windows since cmd.exe doesn't support ANSI color
	// codes. Other terminal may do, but until we can figure that out better
	// disable this for all terms on this platform
	if runtime.GOOS == "windows" {
		color.NoColor = true
		ctx = ctxutil.WithColor(ctx, false)
	}

	return ctx
}
<|endoftext|>


<commit_before>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gxui

import (
	"github.com/google/gxui/math"
)

type Control interface {
	Size() math.Size
	SetSize(math.Size)
	Draw() Canvas
	Parent() Container
	SetParent(Container)
	Attached() bool
	Attach()
	Detach()
	DesiredSize(min, max math.Size) math.Size
	Margin() math.Spacing
	SetMargin(math.Spacing)
	IsVisible() bool
	SetVisible(bool)
	ContainsPoint(math.Point) bool
	IsMouseOver() bool
	IsMouseDown(button MouseButton) bool

	Click(MouseEvent) (consume bool)
	DoubleClick(MouseEvent) (consume bool)
	KeyPress(KeyboardEvent) (consume bool)
	KeyStroke(KeyStrokeEvent) (consume bool)
	MouseScroll(MouseEvent) (consume bool)

	MouseMove(MouseEvent)
	MouseEnter(MouseEvent)
	MouseExit(MouseEvent)
	MouseDown(MouseEvent)
	MouseUp(MouseEvent)
	KeyDown(KeyboardEvent)
	KeyUp(KeyboardEvent)
	KeyRepeat(KeyboardEvent)

	OnAttach(func()) EventSubscription
	OnDetach(func()) EventSubscription
	OnKeyPress(func(KeyboardEvent)) EventSubscription
	OnKeyStroke(func(KeyStrokeEvent)) EventSubscription
	OnClick(func(MouseEvent)) EventSubscription
	OnDoubleClick(func(MouseEvent)) EventSubscription
	OnMouseMove(func(MouseEvent)) EventSubscription
	OnMouseEnter(func(MouseEvent)) EventSubscription
	OnMouseExit(func(MouseEvent)) EventSubscription
	OnMouseDown(func(MouseEvent)) EventSubscription
	OnMouseUp(func(MouseEvent)) EventSubscription
	OnMouseScroll(func(MouseEvent)) EventSubscription
	OnKeyDown(func(KeyboardEvent)) EventSubscription
	OnKeyUp(func(KeyboardEvent)) EventSubscription
	OnKeyRepeat(func(KeyboardEvent)) EventSubscription
}
<commit_msg>Add documentation to the Control interface<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gxui

import "github.com/google/gxui/math"

// Control is the interface exposed by all UI control elements.
type Control interface {
	// Size returns the size of the control. If the control is not attached, then
	// the returned size is undefined.
	Size() math.Size

	// SetSize sets the size of the control to the specified value.
	// SetSize should only be called by the parent of the control during layout.
	SetSize(math.Size)

	// Draw draws the control's visual apperance into the returned, new canvas.
	// Draw is typically called by the parent of the control - calling Draw will
	// not issue a re-draw of an attached control.
	Draw() Canvas

	// Parent returns the parent of the control.
	Parent() Container

	// SetParent sets the parent of the control.
	// SetParent should only be called by the new parent of the control.
	SetParent(Container)

	// Attached returns true if the control is directly or indirectly attached
	// to a window.
	Attached() bool

	// Attach is called when the control is directly or indirectly attached to a
	// window.
	// Attach should only be called by the parent of the control.
	Attach()

	// Detach is called when the control is directly or indirectly detached from a
	// window.
	// Detach should only be called by the parent of the control.
	Detach()

	// DesiredSize returns the desired size of the control based on the min and
	// max size limits. The parent control may ignore the desired size.
	DesiredSize(min, max math.Size) math.Size

	// Margin returns the desired spacing between sibling controls.
	Margin() math.Spacing

	// SetMargin set the desired spacing between sibling controls, issuing a
	// relayout if the margin has changed.
	SetMargin(math.Spacing)

	// IsVisible returns true if the control is visible.
	IsVisible() bool

	// SetVisible sets the visibility of the control.
	SetVisible(bool)

	// ContainsPoint returns true if the specified local-space point is considered
	// within the control.
	ContainsPoint(math.Point) bool

	// IsMouseOver returns true if the mouse cursor was last reported within the
	// control.
	IsMouseOver() bool

	// IsMouseDown returns true if button was last reported pressed on the
	// control.
	IsMouseDown(button MouseButton) bool

	// Click is called when the mouse is pressed and released on the control.
	// If Click returns true, then the click event is consumed by the control,
	// otherwise the next control below the should be considered for the click
	// event.
	Click(MouseEvent) (consume bool)

	// DoubleClick is called when the mouse is double-clicked on the control.
	// If DoubleClick returns true, then the double-click event is consumed by the
	// control, otherwise the next control below the should be considered for the
	// double-click event.
	DoubleClick(MouseEvent) (consume bool)

	// KeyPress is called when a keyboard key is pressed while the control (or
	// non-consuming child) has focus. If KeyPress returns true, then the
	// key-press event is consumed by the control, otherwise the parent control
	// should be considered for the key-press event.
	KeyPress(KeyboardEvent) (consume bool)

	// KeyStroke is called when a key-storke is made while the control (or
	// non-consuming child) has focus. If KeyStroke returns true, then the
	// key-stroke event is consumed by the control, otherwise the parent control
	// should be considered for the key-stroke event.
	KeyStroke(KeyStrokeEvent) (consume bool)

	// MouseScroll is called when a mouse scroll is made while the control (or
	// non-consuming child) has focus. If MouseScroll returns true, then the
	// mouse-scroll event is consumed by the control, otherwise the parent control
	// should be considered for the key-stroke event.
	MouseScroll(MouseEvent) (consume bool)

	// MouseMove is called when the mouse cursor moves over the control.
	MouseMove(MouseEvent)

	// MouseEnter is called when the mouse cursor transitions from outside to
	// inside the bounds of the control.
	MouseEnter(MouseEvent)

	// MouseExit is called when the mouse cursor transitions from inside to
	// outside the bounds of the control.
	MouseExit(MouseEvent)

	// MouseDown is called when a mouse button is pressed while the mouse cursor
	// is over the control.
	MouseDown(MouseEvent)

	// MouseUp is called when a mouse button is released while the mouse cursor
	// is over the control.
	MouseUp(MouseEvent)

	// KeyDown is called when a keyboard button is pressed while the control (or
	// child control) has focus.
	KeyDown(KeyboardEvent)

	// KeyUp is called when a keyboard button is released while the control (or
	// child control) has focus.
	KeyUp(KeyboardEvent)

	// KeyRepeat is called when a keyboard button held long enough for a
	// repeat-key event while the control (or child control) has focus.
	KeyRepeat(KeyboardEvent)

	// OnAttach subscribes f to be called whenever the control is attached.
	OnAttach(f func()) EventSubscription

	// OnDetach subscribes f to be called whenever the control is detached.
	OnDetach(f func()) EventSubscription

	// OnKeyPress subscribes f to be called whenever the control receives a
	// key-press event.
	OnKeyPress(f func(KeyboardEvent)) EventSubscription

	// OnKeyStroke subscribes f to be called whenever the control receives a
	// key-stroke event.
	OnKeyStroke(f func(KeyStrokeEvent)) EventSubscription

	// OnClick subscribes f to be called whenever the control receives a click
	// event.
	OnClick(f func(MouseEvent)) EventSubscription

	// OnDoubleClick subscribes f to be called whenever the control receives a
	// double-click event.
	OnDoubleClick(f func(MouseEvent)) EventSubscription

	// OnMouseMove subscribes f to be called whenever the control receives a
	// mouse-move event.
	OnMouseMove(f func(MouseEvent)) EventSubscription

	// OnMouseEnter subscribes f to be called whenever the control receives a
	// mouse-enter event.
	OnMouseEnter(f func(MouseEvent)) EventSubscription

	// OnMouseExit subscribes f to be called whenever the control receives a
	// mouse-exit event.
	OnMouseExit(f func(MouseEvent)) EventSubscription

	// OnMouseDown subscribes f to be called whenever the control receives a
	// mouse-down event.
	OnMouseDown(f func(MouseEvent)) EventSubscription

	// OnMouseUp subscribes f to be called whenever the control receives a
	// mouse-up event.
	OnMouseUp(f func(MouseEvent)) EventSubscription

	// OnMouseScroll subscribes f to be called whenever the control receives a
	// mouse-scroll event.
	OnMouseScroll(f func(MouseEvent)) EventSubscription

	// OnKeyDown subscribes f to be called whenever the control receives a
	// key-down event.
	OnKeyDown(f func(KeyboardEvent)) EventSubscription

	// OnKeyUp subscribes f to be called whenever the control receives a
	// key-up event.
	OnKeyUp(f func(KeyboardEvent)) EventSubscription

	// OnKeyRepeat subscribes f to be called whenever the control receives a
	// key-repeat event.
	OnKeyRepeat(f func(KeyboardEvent)) EventSubscription
}
<|endoftext|>


<commit_before>// Copyright 2015 LinkedIn Corp. Licensed under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License.
 You may obtain a copy of the License
// at http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
//
distributed under the License is distributed on an "AS IS" BASIS,
//
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.Copyright [201X] LinkedIn Corp. Licensed under the Apache
// License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License.
 You may obtain a copy of
// the License at http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
//
distributed under the License is distributed on an "AS IS" BASIS,
//
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.

package main

import (
	"bytes"
	"github.com/linkedin/goavro"
	"log"
)

func main() {
	recordSchemaJson := `
{
  "type": "record",
  "name": "comments",
  "doc:": "A basic schema for storing blog comments",
  "namespace": "com.example",
  "fields": [
    {
      "doc": "Name of user",
      "type": "string",
      "name": "username"
    },
    {
      "doc": "The content of the user's message",
      "type": "string",
      "name": "comment"
    },
    {
      "doc": "Unix epoch time in milliseconds",
      "type": "long",
      "name": "timestamp"
    }
  ]
}
`
	someRecord, err := goavro.NewRecord(goavro.RecordSchemaJson(recordSchemaJson))
	if err != nil {
		log.Fatal(err)
	}
	// identify field name to set datum for
	someRecord.Set("username", "Aquaman")
	someRecord.Set("comment", "The Atlantic is oddly cold this morning!")
	// you can fully qualified field name
	someRecord.Set("com.example.timestamp", int64(1082196484))

	codec, err := goavro.NewCodec(recordSchemaJson)
	if err != nil {
		log.Fatal(err)
	}

	bb := new(bytes.Buffer)
	if err = codec.Encode(bb, someRecord); err != nil {
		log.Fatal(err)
	}

	actual := bb.Bytes()
	expected := []byte("\x0eAquamanPThe Atlantic is oddly cold this morning!\x88\x88\x88\x88\x08")
	if bytes.Compare(actual, expected) != 0 {
		log.Printf("Actual: %#v; Expected: %#v", actual, expected)
	}
}
<commit_msg>comment typo (qualify is spelled correctly)<commit_after>// Copyright 2015 LinkedIn Corp. Licensed under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License.
 You may obtain a copy of the License
// at http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
//
distributed under the License is distributed on an "AS IS" BASIS,
//
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.Copyright [201X] LinkedIn Corp. Licensed under the Apache
// License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License.
 You may obtain a copy of
// the License at http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
//
distributed under the License is distributed on an "AS IS" BASIS,
//
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.

package main

import (
	"bytes"
	"github.com/linkedin/goavro"
	"log"
)

func main() {
	recordSchemaJson := `
{
  "type": "record",
  "name": "comments",
  "doc:": "A basic schema for storing blog comments",
  "namespace": "com.example",
  "fields": [
    {
      "doc": "Name of user",
      "type": "string",
      "name": "username"
    },
    {
      "doc": "The content of the user's message",
      "type": "string",
      "name": "comment"
    },
    {
      "doc": "Unix epoch time in milliseconds",
      "type": "long",
      "name": "timestamp"
    }
  ]
}
`
	someRecord, err := goavro.NewRecord(goavro.RecordSchemaJson(recordSchemaJson))
	if err != nil {
		log.Fatal(err)
	}
	// identify field name to set datum for
	someRecord.Set("username", "Aquaman")
	someRecord.Set("comment", "The Atlantic is oddly cold this morning!")
	// you can fully qualify the field name
	someRecord.Set("com.example.timestamp", int64(1082196484))

	codec, err := goavro.NewCodec(recordSchemaJson)
	if err != nil {
		log.Fatal(err)
	}

	bb := new(bytes.Buffer)
	if err = codec.Encode(bb, someRecord); err != nil {
		log.Fatal(err)
	}

	actual := bb.Bytes()
	expected := []byte("\x0eAquamanPThe Atlantic is oddly cold this morning!\x88\x88\x88\x88\x08")
	if bytes.Compare(actual, expected) != 0 {
		log.Printf("Actual: %#v; Expected: %#v", actual, expected)
	}
}
<|endoftext|>


<commit_before>// Basic data structures for Go.
package main

import (
	"fmt"
)

// Letters for quick lookup.
// These are the letters marked on top of the board.  "I" is
// always left out, likely to avoid confusion with "1".
const BoardLetters = "ABCDEFGHJKLMNOPQRST"
const ColorChars = "CBW"

// Here it would be nice if the type system allows us to say
// "integer with one of these value: 0, 1, 2.
type Stone int

const (
	// Stone colors. Used for board state.
	Empty = 0 // No stone here.
	Black = 1
	White = 2

	// Moves. These correspond to board state making it easy to
	// translate between the two. (A capture will clear that position on
	// the board.)
	Capture    = 0
	PlaceBlack = 1
	PlaceWhite = 2
)

// Position where a stone can be placed.
type Pos struct {
	X int // Left to right, 0-18 (marked as 1-19 on board.)
	Y int // Bottom to top, 0-18 (ABCDEFGHJKLMNOPQRST.)
}

type BoardSize struct {
	Width  int
	Height int
}

// A move. (Place or capture stone.)
type Move struct {
	Action Stone // (Hmm, this looks a bit weird here.)
	Pos
}

func (move Move) String() string {
	// Todo: check if values are out of range?
	color := ColorChars[move.Action]
	letter := BoardLetters[move.X]
	number := move.Y + 1

	return fmt.Sprintf("%c%c%d", color, letter, number)
}

// The board is just a cache of moves. We can recompute the board from
// the moves.
type Game struct {
	BoardSize
	Board map[Pos]Stone
	Moves []Move
	// Here we should have a stack (or slice) of boards so that we have
	// a history of previous board states.

	// + We need to keep score somehow. (Number of captured stones for
	// each player.)
}

func (game *Game) AddMove(move Move) {
	game.Moves = append(game.Moves, move)
	game.Board[move.Pos] = move.Action
}

func (game *Game) AddMoves(moves []Move) {
	for _, move := range moves {
		fmt.Println(move)
		game.AddMove(move)
	}
}

func (game *Game) PrintBoard() {
	stoneChars := ".BW"

	fmt.Println("   " + BoardLetters)

	for y := game.Height - 1; y >= 0; y-- {
		line := ""
		for x := 0; x < game.Width; x++ {
			line += fmt.Sprintf("%c", stoneChars[game.Board[Pos{x, y}]])
		}
		fmt.Printf("%2d %s\n", y+1, line)
	}
}

func NewGame(boardSize BoardSize) Game {
	return Game{
		BoardSize: boardSize,
		Board:     make(map[Pos]Stone),
	}
}

func main() {
	game := NewGame(BoardSize{19, 19})
	moves := []Move{{PlaceBlack, Pos{0, 0}}, {PlaceWhite, Pos{1, 6}}}
	game.AddMoves(moves)

	game.PrintBoard()

	// fmt.Println(game.Board[Pos{}])
	// fmt.Println(game.Board[Pos{1, 1}])

	// fmt.Println(moves)
}
<commit_msg>Color characters (b and w) had reverted to uppercase somehow.<commit_after>// Basic data structures for Go.
package main

import (
	"fmt"
)

// Letters for quick lookup.
// These are the letters marked on top of the board.  "I" is
// always left out, likely to avoid confusion with "1".
const BoardLetters = "ABCDEFGHJKLMNOPQRST"
const ColorChars = "cbw"

// Here it would be nice if the type system allows us to say
// "integer with one of these value: 0, 1, 2.
type Stone int

const (
	// Stone colors. Used for board state.
	Empty = 0 // No stone here.
	Black = 1
	White = 2

	// Moves. These correspond to board state making it easy to
	// translate between the two. (A capture will clear that position on
	// the board.)
	Capture    = 0
	PlaceBlack = 1
	PlaceWhite = 2
)

// Position where a stone can be placed.
type Pos struct {
	X int // Left to right, 0-18 (marked as 1-19 on board.)
	Y int // Bottom to top, 0-18 (ABCDEFGHJKLMNOPQRST.)
}

type BoardSize struct {
	Width  int
	Height int
}

// A move. (Place or capture stone.)
type Move struct {
	Action Stone // (Hmm, this looks a bit weird here.)
	Pos
}

func (move Move) String() string {
	// Todo: check if values are out of range?
	color := ColorChars[move.Action]
	letter := BoardLetters[move.X]
	number := move.Y + 1

	return fmt.Sprintf("%c%c%d", color, letter, number)
}

// The board is just a cache of moves. We can recompute the board from
// the moves.
type Game struct {
	BoardSize
	Board map[Pos]Stone
	Moves []Move
	// Here we should have a stack (or slice) of boards so that we have
	// a history of previous board states.

	// + We need to keep score somehow. (Number of captured stones for
	// each player.)
}

func (game *Game) AddMove(move Move) {
	game.Moves = append(game.Moves, move)
	game.Board[move.Pos] = move.Action
}

func (game *Game) AddMoves(moves []Move) {
	for _, move := range moves {
		fmt.Println(move)
		game.AddMove(move)
	}
}

func (game *Game) PrintBoard() {
	stoneChars := ".BW"

	fmt.Println("   " + BoardLetters)

	for y := game.Height - 1; y >= 0; y-- {
		line := ""
		for x := 0; x < game.Width; x++ {
			line += fmt.Sprintf("%c", stoneChars[game.Board[Pos{x, y}]])
		}
		fmt.Printf("%2d %s\n", y+1, line)
	}
}

func NewGame(boardSize BoardSize) Game {
	return Game{
		BoardSize: boardSize,
		Board:     make(map[Pos]Stone),
	}
}

func main() {
	game := NewGame(BoardSize{19, 19})
	moves := []Move{{PlaceBlack, Pos{0, 0}}, {PlaceWhite, Pos{1, 6}}}
	game.AddMoves(moves)

	game.PrintBoard()

	// fmt.Println(game.Board[Pos{}])
	// fmt.Println(game.Board[Pos{1, 1}])

	// fmt.Println(moves)
}
<|endoftext|>


<commit_before>// Copyright 2014 PagerDuty, Inc.
// All rights reserved - Do not redistribute!

package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/PagerDuty/godspeed"
	"github.com/codeskyblue/go-uuid"
	"github.com/jessevdk/go-flags"
)

// MaxBody is the maximum length of a event body
const MaxBody = 4096

// args is for argument parsing
type args struct {
	Label     string `short:"l" long:"label" default:"" description:"name for cron job to be used in statsd emissions and DogStatsd events. alphanumeric only; cronner will lowercase it"`
	Cmd       string `short:"c" long:"command" default:"" description:"command to run (please use full path) and its args; executed as user running cronner"`
	AllEvents bool   `short:"e" long:"event" default:"false" description:"emit a start and end datadog event"`
	FailEvent bool   `short:"E" long:"event-fail" default:"false" description:"only emit an event on failure"`
}

// parse function configures the go-flags parser and runs it
// it also does some light input validation
func (a *args) parse() error {
	p := flags.NewParser(a, flags.HelpFlag|flags.PassDoubleDash)

	_, err := p.Parse()

	// determine if there was a parsing error
	// unfortunately, help message is returned as an error
	if err != nil {
		if !strings.Contains(err.Error(), "Usage") {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
			os.Exit(-1)
		} else {
			fmt.Printf("%v\n", err.Error())
			os.Exit(0)
		}
	}

	r := regexp.MustCompile("^[a-zA-Z0-9_\\.]+$")

	if !r.MatchString(a.Label) {
		return fmt.Errorf("cron label '%v' is invalid, it can only be alphanumeric with underscores and periods", a.Label)
	}

	if a.Cmd == "" {
		return fmt.Errorf("you must specify a command to run")
	}

	// lowercase the metric to try and encourage sanity
	a.Label = strings.ToLower(a.Label)

	return nil
}

func runCommand(cmd *exec.Cmd, label string, gs *godspeed.Godspeed) (int, []byte, float64, error) {
	var b bytes.Buffer

	// comnbine stdout and stderr to the same buffer
	cmd.Stdout = &b
	cmd.Stderr = &b

	// log start time
	s := time.Now().UTC()

	err := cmd.Run()

	// This next section computes the wallclock run time in ms.
	// However, there is the unfortunate limitation in that
	// it uses the clock that gets adjusted by ntpd. Within pure
	// Go, I don't have access to CLOCK_MONOTONIC_RAW.
	//
	// However, based on our usage I don't think we care about it
	// being off by a few milliseconds.
	t := time.Since(s).Seconds() * 1000

	var ret int

	// if the command failed we want the exit status code
	// and to change the state variables to their failure values
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			status := ee.Sys().(syscall.WaitStatus)
			ret = status.ExitStatus()
		} else {
			ret = -1
		}
	}

	// emit the metric for how long it took us and return code
	gs.Timing(fmt.Sprintf("cron.%v.time", label), t, nil)
	gs.Gauge(fmt.Sprintf("cron.%v.exit_code", label), float64(ret), nil)

	return ret, b.Bytes(), t, err
}

// emit a godspeed (dogstatsd) event
func emitEvent(title, body, label, alertType, uuidStr string, g *godspeed.Godspeed) {
	var buf bytes.Buffer

	// if the event's body is bigger than MaxBody
	if len(body) > MaxBody {
		// push the first MaxBody/2 bytes in to the buffer
		buf.WriteString(body[0 : MaxBody/2])

		// add indication of truncated output to the buffer
		buf.WriteString("...\n=== OUTPUT TRUNCATED ===\n")

		// add the last 1024 bytes to the buffer
		buf.WriteString(body[len(body)-((MaxBody/2)+1) : len(body)-1])

		body = string(buf.Bytes())
	}

	fields := make(map[string]string)
	fields["source_type_name"] = "cron"

	if len(alertType) > 0 {
		fields["alert_type"] = alertType
	}

	if len(uuidStr) > 0 {
		fields["aggregation_key"] = uuidStr
	}

	tags := []string{"source_type:cron", fmt.Sprintf("label_name:%v", label)}

	g.Event(title, body, fields, tags)
}

func main() {
	// get and parse the command line options
	opts := &args{}
	err := opts.parse()

	// make sure parsing didn't bomb
	if err != nil {
		// print the error to stderr and exit -1
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(-1)
	}

	// build a Godspeed client
	gs, err := godspeed.NewDefault()

	// make sure nothing went wrong with Godspeed
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(-1)
	}

	gs.SetNamespace("pagerduty")

	// get the hostname and validate nothing happened
	hostname, err := os.Hostname()

	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(-1)
	}

	// split the command in to its binary and arguments
	cmdParts := strings.Split(opts.Cmd, " ")

	// build the args slice
	var args []string
	if len(cmdParts) > 1 {
		args = cmdParts[1:]
	}

	// get the *exec.Cmd instance
	cmd := exec.Command(cmdParts[0], args...)

	var uuidStr string

	if opts.AllEvents {
		uuidStr = uuid.New()
		// emit a DD event to indicate we are starting the job
		emitEvent(fmt.Sprintf("Cron %v starting on %v", opts.Label, hostname), "job starting", opts.Label, "info", uuidStr, gs)
	}

	// run the command and return the output as well as the return status
	ret, out, wallRtMs, err := runCommand(cmd, opts.Label, gs)

	// default variables are for success
	// we change them later if there was a failure
	msg := "succeeded"
	alertType := "success"

	// if the command failed change the state variables to their failure values
	if err != nil {
		msg = "failed"
		alertType = "error"
	}

	if opts.AllEvents || (opts.FailEvent && alertType == "error") {
		// build the pieces of the completion event
		title := fmt.Sprintf("Cron %v %v in %.5f seconds on %v", opts.Label, msg, wallRtMs/1000, hostname)

		body := fmt.Sprintf("exit code: %d\n", ret)
		if err != nil {
			er := regexp.MustCompile("^exit status ([-]?\\d)")

			// do not show the 'more:' line, if the line is just telling us
			// what the exit code is
			if !er.MatchString(err.Error()) {
				body = fmt.Sprintf("%vmore: %v\n", body, err.Error())
			}
		}

		var cmdOutput string

		if len(out) > 0 {
			cmdOutput = string(out)
		} else {
			cmdOutput = "(none)"
		}

		body = fmt.Sprintf("%voutput: %v", body, cmdOutput)

		if uuidStr == "" {
			uuidStr = uuid.New()
		}

		emitEvent(title, body, opts.Label, alertType, uuidStr, gs)
	}
}
<commit_msg>add UUID value to the body of the event emission<commit_after>// Copyright 2014 PagerDuty, Inc.
// All rights reserved - Do not redistribute!

package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/PagerDuty/godspeed"
	"github.com/codeskyblue/go-uuid"
	"github.com/jessevdk/go-flags"
)

// MaxBody is the maximum length of a event body
const MaxBody = 4096

// args is for argument parsing
type args struct {
	Label     string `short:"l" long:"label" default:"" description:"name for cron job to be used in statsd emissions and DogStatsd events. alphanumeric only; cronner will lowercase it"`
	Cmd       string `short:"c" long:"command" default:"" description:"command to run (please use full path) and its args; executed as user running cronner"`
	AllEvents bool   `short:"e" long:"event" default:"false" description:"emit a start and end datadog event"`
	FailEvent bool   `short:"E" long:"event-fail" default:"false" description:"only emit an event on failure"`
}

// parse function configures the go-flags parser and runs it
// it also does some light input validation
func (a *args) parse() error {
	p := flags.NewParser(a, flags.HelpFlag|flags.PassDoubleDash)

	_, err := p.Parse()

	// determine if there was a parsing error
	// unfortunately, help message is returned as an error
	if err != nil {
		if !strings.Contains(err.Error(), "Usage") {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
			os.Exit(-1)
		} else {
			fmt.Printf("%v\n", err.Error())
			os.Exit(0)
		}
	}

	r := regexp.MustCompile("^[a-zA-Z0-9_\\.]+$")

	if !r.MatchString(a.Label) {
		return fmt.Errorf("cron label '%v' is invalid, it can only be alphanumeric with underscores and periods", a.Label)
	}

	if a.Cmd == "" {
		return fmt.Errorf("you must specify a command to run")
	}

	// lowercase the metric to try and encourage sanity
	a.Label = strings.ToLower(a.Label)

	return nil
}

func runCommand(cmd *exec.Cmd, label string, gs *godspeed.Godspeed) (int, []byte, float64, error) {
	var b bytes.Buffer

	// comnbine stdout and stderr to the same buffer
	cmd.Stdout = &b
	cmd.Stderr = &b

	// log start time
	s := time.Now().UTC()

	err := cmd.Run()

	// This next section computes the wallclock run time in ms.
	// However, there is the unfortunate limitation in that
	// it uses the clock that gets adjusted by ntpd. Within pure
	// Go, I don't have access to CLOCK_MONOTONIC_RAW.
	//
	// However, based on our usage I don't think we care about it
	// being off by a few milliseconds.
	t := time.Since(s).Seconds() * 1000

	var ret int

	// if the command failed we want the exit status code
	// and to change the state variables to their failure values
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			status := ee.Sys().(syscall.WaitStatus)
			ret = status.ExitStatus()
		} else {
			ret = -1
		}
	}

	// emit the metric for how long it took us and return code
	gs.Timing(fmt.Sprintf("cron.%v.time", label), t, nil)
	gs.Gauge(fmt.Sprintf("cron.%v.exit_code", label), float64(ret), nil)

	return ret, b.Bytes(), t, err
}

// emit a godspeed (dogstatsd) event
func emitEvent(title, body, label, alertType, uuidStr string, g *godspeed.Godspeed) {
	var buf bytes.Buffer

	// if the event's body is bigger than MaxBody
	if len(body) > MaxBody {
		// push the first MaxBody/2 bytes in to the buffer
		buf.WriteString(body[0 : MaxBody/2])

		// add indication of truncated output to the buffer
		buf.WriteString("...\n=== OUTPUT TRUNCATED ===\n")

		// add the last 1024 bytes to the buffer
		buf.WriteString(body[len(body)-((MaxBody/2)+1) : len(body)-1])

		body = string(buf.Bytes())
	}

	fields := make(map[string]string)
	fields["source_type_name"] = "cron"

	if len(alertType) > 0 {
		fields["alert_type"] = alertType
	}

	if len(uuidStr) > 0 {
		fields["aggregation_key"] = uuidStr
	}

	tags := []string{"source_type:cron", fmt.Sprintf("label_name:%v", label)}

	g.Event(title, body, fields, tags)
}

func main() {
	// get and parse the command line options
	opts := &args{}
	err := opts.parse()

	// make sure parsing didn't bomb
	if err != nil {
		// print the error to stderr and exit -1
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(-1)
	}

	// build a Godspeed client
	gs, err := godspeed.NewDefault()

	// make sure nothing went wrong with Godspeed
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(-1)
	}

	gs.SetNamespace("pagerduty")

	// get the hostname and validate nothing happened
	hostname, err := os.Hostname()

	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(-1)
	}

	// split the command in to its binary and arguments
	cmdParts := strings.Split(opts.Cmd, " ")

	// build the args slice
	var args []string
	if len(cmdParts) > 1 {
		args = cmdParts[1:]
	}

	// get the *exec.Cmd instance
	cmd := exec.Command(cmdParts[0], args...)

	var uuidStr string

	if opts.AllEvents {
		uuidStr = uuid.New()
		// emit a DD event to indicate we are starting the job
		emitEvent(fmt.Sprintf("Cron %v starting on %v", opts.Label, hostname), fmt.Sprintf("UUID:%v\n", uuidStr), opts.Label, "info", uuidStr, gs)
	}

	// run the command and return the output as well as the return status
	ret, out, wallRtMs, err := runCommand(cmd, opts.Label, gs)

	// default variables are for success
	// we change them later if there was a failure
	msg := "succeeded"
	alertType := "success"

	// if the command failed change the state variables to their failure values
	if err != nil {
		msg = "failed"
		alertType = "error"
	}

	if opts.AllEvents || (opts.FailEvent && alertType == "error") {
		// build the pieces of the completion event
		title := fmt.Sprintf("Cron %v %v in %.5f seconds on %v", opts.Label, msg, wallRtMs/1000, hostname)

		if uuidStr == "" {
			uuidStr = uuid.New()
		}

		body := fmt.Sprintf("UUID: %v\nexit code: %d\n", uuidStr, ret)
		if err != nil {
			er := regexp.MustCompile("^exit status ([-]?\\d)")

			// do not show the 'more:' line, if the line is just telling us
			// what the exit code is
			if !er.MatchString(err.Error()) {
				body = fmt.Sprintf("%vmore: %v\n", body, err.Error())
			}
		}

		var cmdOutput string

		if len(out) > 0 {
			cmdOutput = string(out)
		} else {
			cmdOutput = "(none)"
		}

		body = fmt.Sprintf("%voutput: %v", body, cmdOutput)

		emitEvent(title, body, opts.Label, alertType, uuidStr, gs)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/reviewdog/reviewdog/doghouse"
	"github.com/reviewdog/reviewdog/doghouse/server"
	"github.com/reviewdog/reviewdog/doghouse/server/ciutil"
	"github.com/reviewdog/reviewdog/doghouse/server/storage"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

type githubChecker struct {
	privateKey       []byte
	integrationID    int
	ghInstStore      storage.GitHubInstallationStore
	ghRepoTokenStore storage.GitHubRepositoryTokenStore
}

func (gc *githubChecker) handleCheck(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	ctx := appengine.NewContext(r)

	var req doghouse.CheckRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "failed to decode request: %v", err)
		return
	}

	// Check authorization.
	if !gc.validateCheckRequest(ctx, w, r, req.Owner, req.Repo) {
		return
	}

	opt := &server.NewGitHubClientOption{
		PrivateKey:        gc.privateKey,
		IntegrationID:     gc.integrationID,
		RepoOwner:         req.Owner,
		Client:            urlfetch.Client(ctx),
		InstallationStore: gc.ghInstStore,
	}

	gh, err := server.NewGitHubClient(ctx, opt)
	if err != nil {
		log.Errorf(ctx, "failed to create GitHub client: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, err)
		return
	}

	res, err := server.NewChecker(&req, gh).Check(ctx)
	if err != nil {
		log.Errorf(ctx, "failed to run checker: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, err)
		return
	}
	if err := json.NewEncoder(w).Encode(res); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, err)
		return
	}
}

func (gc *githubChecker) validateCheckRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, owner, repo string) bool {
	log.Infof(ctx, "Remote Addr: %s", r.RemoteAddr)
	// Always update Travis IP Addresss before checking IP to reduce the # of
	// flaky errors.
	if err := ciutil.UpdateTravisCIIPAddrs(urlfetch.Client(ctx)); err != nil {
		log.Errorf(ctx, "failed to update travis CI IP addresses: %v", err)
	}
	if ciutil.IsFromCI(r) {
		// Skip token validation if it's from trusted CI providers.
		return true
	}
	return gc.validateCheckToken(ctx, w, r, owner, repo)
}

func (gc *githubChecker) validateCheckToken(ctx context.Context, w http.ResponseWriter, r *http.Request, owner, repo string) bool {
	token := extractBearerToken(r)
	if token == "" {
		w.Header().Set("The WWW-Authenticate", `error="invalid_request", error_description="The access token not provided"`)
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprintf(w, "The access token not provided. Get token from %s", githubRepoURL(ctx, r, owner, repo))
		return false
	}
	_, wantToken, err := gc.ghRepoTokenStore.Get(ctx, owner, repo)
	if err != nil {
		log.Errorf(ctx, "failed to get repository (%s/%s) token: %v", owner, repo, err)
	}
	if wantToken == nil {
		w.WriteHeader(http.StatusNotFound)
		return false
	}
	if token != wantToken.Token {
		w.Header().Set("The WWW-Authenticate", `error="invalid_token", error_description="The access token is invalid"`)
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprintf(w, "The access token is invalid. Get valid token from %s", githubRepoURL(ctx, r, owner, repo))
		return false
	}
	return true
}

func githubRepoURL(ctx context.Context, r *http.Request, owner, repo string) string {
	u := doghouseBaseURL(ctx, r)
	u.Path = fmt.Sprintf("/gh/%s/%s", owner, repo)
	return u.String()
}

func doghouseBaseURL(ctx context.Context, r *http.Request) *url.URL {
	scheme := ""
	if r.URL != nil && r.URL.Scheme != "" {
		scheme = r.URL.Scheme
	}
	if scheme == "" {
		scheme = "https"
		if appengine.IsDevAppServer() {
			scheme = "http"
		}
	}
	u, err := url.Parse(scheme + "://" + r.Host)
	if err != nil {
		log.Errorf(ctx, "%v", err)
	}
	return u
}

func extractBearerToken(r *http.Request) string {
	auth := r.Header.Get("Authorization")
	prefix := "bearer "
	if strings.HasPrefix(strings.ToLower(auth), prefix) {
		return auth[len(prefix):]
	}
	return ""
}
<commit_msg>Check only when token is not present<commit_after>package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/reviewdog/reviewdog/doghouse"
	"github.com/reviewdog/reviewdog/doghouse/server"
	"github.com/reviewdog/reviewdog/doghouse/server/ciutil"
	"github.com/reviewdog/reviewdog/doghouse/server/storage"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

type githubChecker struct {
	privateKey       []byte
	integrationID    int
	ghInstStore      storage.GitHubInstallationStore
	ghRepoTokenStore storage.GitHubRepositoryTokenStore
}

func (gc *githubChecker) handleCheck(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	ctx := appengine.NewContext(r)

	var req doghouse.CheckRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "failed to decode request: %v", err)
		return
	}

	// Check authorization.
	if !gc.validateCheckRequest(ctx, w, r, req.Owner, req.Repo) {
		return
	}

	opt := &server.NewGitHubClientOption{
		PrivateKey:        gc.privateKey,
		IntegrationID:     gc.integrationID,
		RepoOwner:         req.Owner,
		Client:            urlfetch.Client(ctx),
		InstallationStore: gc.ghInstStore,
	}

	gh, err := server.NewGitHubClient(ctx, opt)
	if err != nil {
		log.Errorf(ctx, "failed to create GitHub client: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, err)
		return
	}

	res, err := server.NewChecker(&req, gh).Check(ctx)
	if err != nil {
		log.Errorf(ctx, "failed to run checker: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, err)
		return
	}
	if err := json.NewEncoder(w).Encode(res); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintln(w, err)
		return
	}
}

func (gc *githubChecker) validateCheckRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, owner, repo string) bool {
	if extractBearerToken(r) == "" {
		// Update Travis IP Addresss before checking IP to reduce the # of
		// flaky errors when token is not present.
		if err := ciutil.UpdateTravisCIIPAddrs(urlfetch.Client(ctx)); err != nil {
			log.Errorf(ctx, "failed to update travis CI IP addresses: %v", err)
		}
	}
	log.Infof(ctx, "Remote Addr: %s", r.RemoteAddr)
	if ciutil.IsFromCI(r) {
		// Skip token validation if it's from trusted CI providers.
		return true
	}
	return gc.validateCheckToken(ctx, w, r, owner, repo)
}

func (gc *githubChecker) validateCheckToken(ctx context.Context, w http.ResponseWriter, r *http.Request, owner, repo string) bool {
	token := extractBearerToken(r)
	if token == "" {
		w.Header().Set("The WWW-Authenticate", `error="invalid_request", error_description="The access token not provided"`)
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprintf(w, "The access token not provided. Get token from %s", githubRepoURL(ctx, r, owner, repo))
		return false
	}
	_, wantToken, err := gc.ghRepoTokenStore.Get(ctx, owner, repo)
	if err != nil {
		log.Errorf(ctx, "failed to get repository (%s/%s) token: %v", owner, repo, err)
	}
	if wantToken == nil {
		w.WriteHeader(http.StatusNotFound)
		return false
	}
	if token != wantToken.Token {
		w.Header().Set("The WWW-Authenticate", `error="invalid_token", error_description="The access token is invalid"`)
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprintf(w, "The access token is invalid. Get valid token from %s", githubRepoURL(ctx, r, owner, repo))
		return false
	}
	return true
}

func githubRepoURL(ctx context.Context, r *http.Request, owner, repo string) string {
	u := doghouseBaseURL(ctx, r)
	u.Path = fmt.Sprintf("/gh/%s/%s", owner, repo)
	return u.String()
}

func doghouseBaseURL(ctx context.Context, r *http.Request) *url.URL {
	scheme := ""
	if r.URL != nil && r.URL.Scheme != "" {
		scheme = r.URL.Scheme
	}
	if scheme == "" {
		scheme = "https"
		if appengine.IsDevAppServer() {
			scheme = "http"
		}
	}
	u, err := url.Parse(scheme + "://" + r.Host)
	if err != nil {
		log.Errorf(ctx, "%v", err)
	}
	return u
}

func extractBearerToken(r *http.Request) string {
	auth := r.Header.Get("Authorization")
	prefix := "bearer "
	if strings.HasPrefix(strings.ToLower(auth), prefix) {
		return auth[len(prefix):]
	}
	return ""
}
<|endoftext|>


<commit_before>package api

import (
	"io"
	"log"
	"mime"
	"net/http"
	"path"
	"strconv"
	"sync/atomic"
	"time"

	"github.com/mdlayher/wavepipe/data"
)

// httpStream provides a common method to transfer a file stream using a HTTP response writer
// TODO: use this for transcoded file streams later on as well
func httpStream(song *data.Song, stream io.ReadCloser, httpRes http.ResponseWriter) error {
	// Total bytes transferred
	var total int64

	// Track the stream's progress via log
	stopProgressChan := make(chan struct{})
	go func() {
		// Track start time
		startTime := time.Now()

		// Print progress every 5 seconds
		progress := time.NewTicker(5 * time.Second)

		// Calculate total file size
		totalSize := float64(song.FileSize) / 1024 / 1024
		for {
			select {
			// Print progress
			case <-progress.C:
				// Capture current progress
				currTotal := atomic.LoadInt64(&total)
				current := float64(currTotal) / 1024 / 1024

				// Capture current percentage
				percent := int64(float64(float64(currTotal)/float64(song.FileSize)) * 100)

				// Capture current transfer rate
				rate := float64(float64((currTotal*8)/1024/1024) / float64(time.Now().Sub(startTime).Seconds()))

				log.Printf("[%d] [%03d%%] %02.3f / %02.3f MB [%02.3f Mbps]", song.ID, percent, current, totalSize, rate)
			// Stop printing
			case <-stopProgressChan:
				return
			}
		}
	}()

	// Stop progress on return
	defer func() {
		stopProgressChan <- struct{}{}
	}()

	// Buffer to store and transfer file bytes
	buf := make([]byte, 8192)

	// Indicate when stream is complete
	streamComplete := false

	// Set necessary output HTTP headers
	httpRes.Header().Set("Content-Length", strconv.FormatInt(song.FileSize, 10))
	httpRes.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(song.FileName)))
	httpRes.Header().Set("Last-Modified", time.Unix(song.LastModified, 0).UTC().Format(time.RFC1123))

	// Begin transferring the data stream
	for {
		// Read in a buffer from the file
		n, err := stream.Read(buf)
		if err != nil && err != io.EOF {
			return err
		} else if err == io.EOF {
			// Halt streaming after next write
			streamComplete = true
		}

		// Count bytes
		atomic.AddInt64(&total, int64(n))

		// Write bytes over HTTP
		if _, err := httpRes.Write(buf[:n]); err != nil && err != io.EOF {
			return err
		}

		// If stream is complete, break loop
		if streamComplete {
			break
		}
	}

	// No errors
	return nil
}
<commit_msg>Add fileSize parameter to api.httpStream(), so we can later estimate transcode sizes<commit_after>package api

import (
	"io"
	"log"
	"net/http"
	"strconv"
	"sync/atomic"
	"time"

	"github.com/mdlayher/wavepipe/data"
)

// httpStream provides a common method to transfer a file stream using a HTTP response writer
// TODO: use this for transcoded file streams later on as well
func httpStream(song *data.Song, fileSize int64, stream io.ReadCloser, httpRes http.ResponseWriter) error {
	// Total bytes transferred
	var total int64

	// Track the stream's progress via log
	stopProgressChan := make(chan struct{})
	go func() {
		// Track start time
		startTime := time.Now()

		// Print progress every 5 seconds
		progress := time.NewTicker(5 * time.Second)

		// Calculate total file size
		totalSize := float64(fileSize) / 1024 / 1024
		for {
			select {
			// Print progress
			case <-progress.C:
				// Capture current progress
				currTotal := atomic.LoadInt64(&total)
				current := float64(currTotal) / 1024 / 1024

				// Capture current percentage
				percent := int64(float64(float64(currTotal)/float64(fileSize)) * 100)

				// Capture current transfer rate
				rate := float64(float64((currTotal*8)/1024/1024) / float64(time.Now().Sub(startTime).Seconds()))

				log.Printf("[%d] [%03d%%] %02.3f / %02.3f MB [%02.3f Mbps]", song.ID, percent, current, totalSize, rate)
			// Stop printing
			case <-stopProgressChan:
				return
			}
		}
	}()

	// Stop progress on return
	defer func() {
		stopProgressChan <- struct{}{}
	}()

	// Buffer to store and transfer file bytes
	buf := make([]byte, 8192)

	// Indicate when stream is complete
	streamComplete := false

	// Set necessary output HTTP headers

	// Set Content-Length if set
	if fileSize > 0 {
		httpRes.Header().Set("Content-Length", strconv.FormatInt(fileSize, 10))
	}

	// Set Last-Modified using filesystem modify time
	httpRes.Header().Set("Last-Modified", time.Unix(song.LastModified, 0).UTC().Format(time.RFC1123))

	// Begin transferring the data stream
	for {
		// Read in a buffer from the file
		n, err := stream.Read(buf)
		if err != nil && err != io.EOF {
			return err
		} else if err == io.EOF {
			// Halt streaming after next write
			streamComplete = true
		}

		// Count bytes
		atomic.AddInt64(&total, int64(n))

		// Write bytes over HTTP
		if _, err := httpRes.Write(buf[:n]); err != nil && err != io.EOF {
			return err
		}

		// If stream is complete, break loop
		if streamComplete {
			break
		}
	}

	// No errors
	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/hashicorp/nomad/testutil"
)

func TestNodes_List(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	var qm *QueryMeta
	var out []*NodeListStub
	var err error

	testutil.WaitForResult(func() (bool, error) {
		out, qm, err = nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Check that we got valid QueryMeta.
	assertQueryMeta(t, qm)
}

func TestNodes_PrefixList(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	var qm *QueryMeta
	var out []*NodeListStub
	var err error

	// Get the node ID
	var nodeID string
	testutil.WaitForResult(func() (bool, error) {
		out, _, err := nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		nodeID = out[0].ID
		_ = out[0].Datacenter
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Find node based on four character prefix
	out, qm, err = nodes.PrefixList(nodeID[:4])
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if n := len(out); n != 1 {
		t.Fatalf("expected 1 node, got: %d ", n)
	}

	// Check that we got valid QueryMeta.
	assertQueryMeta(t, qm)
}

func TestNodes_Info(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	// Retrieving a non-existent node returns error
	_, _, err := nodes.Info("12345678-abcd-efab-cdef-123456789abc", nil)
	if err == nil || !strings.Contains(err.Error(), "not found") {
		t.Fatalf("expected not found error, got: %#v", err)
	}

	// Get the node ID
	var nodeID, dc string
	testutil.WaitForResult(func() (bool, error) {
		out, _, err := nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		nodeID = out[0].ID
		dc = out[0].Datacenter
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Querying for existing nodes returns properly
	result, qm, err := nodes.Info(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	assertQueryMeta(t, qm)

	// Check that the result is what we expect
	if result.ID != nodeID || result.Datacenter != dc {
		t.Fatalf("expected %s (%s), got: %s (%s)",
			nodeID, dc,
			result.ID, result.Datacenter)
	}
}

func TestNodes_ToggleDrain(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	// Wait for node registration and get the ID
	var nodeID string
	testutil.WaitForResult(func() (bool, error) {
		out, _, err := nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		nodeID = out[0].ID
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Check for drain mode
	out, _, err := nodes.Info(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if out.Drain {
		t.Fatalf("drain mode should be off")
	}

	// Toggle it on
	wm, err := nodes.ToggleDrain(nodeID, true, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	assertWriteMeta(t, wm)

	// Check again
	out, _, err = nodes.Info(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if !out.Drain {
		t.Fatalf("drain mode should be on")
	}

	// Toggle off again
	wm, err = nodes.ToggleDrain(nodeID, false, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	assertWriteMeta(t, wm)

	// Check again
	out, _, err = nodes.Info(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if out.Drain {
		t.Fatalf("drain mode should be off")
	}
}

func TestNodes_Allocations(t *testing.T) {
	c, s := makeClient(t, nil, nil)
	defer s.Stop()
	nodes := c.Nodes()

	// Looking up by a non-existent node returns nothing. We
	// don't check the index here because it's possible the node
	// has already registered, in which case we will get a non-
	// zero result anyways.
	allocs, _, err := nodes.Allocations("nope", nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if n := len(allocs); n != 0 {
		t.Fatalf("expected 0 allocs, got: %d", n)
	}
}

func TestNodes_ForceEvaluate(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	// Force-eval on a non-existent node fails
	_, _, err := nodes.ForceEvaluate("12345678-abcd-efab-cdef-123456789abc", nil)
	if err == nil || !strings.Contains(err.Error(), "not found") {
		t.Fatalf("expected not found error, got: %#v", err)
	}

	// Wait for node registration and get the ID
	var nodeID string
	testutil.WaitForResult(func() (bool, error) {
		out, _, err := nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		nodeID = out[0].ID
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Try force-eval again. We don't check the WriteMeta because
	// there are no allocations to process, so we would get an index
	// of zero. Same goes for the eval ID.
	_, _, err = nodes.ForceEvaluate(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestNodes_Sort(t *testing.T) {
	nodes := []*NodeListStub{
		&NodeListStub{CreateIndex: 2},
		&NodeListStub{CreateIndex: 1},
		&NodeListStub{CreateIndex: 5},
	}
	sort.Sort(NodeIndexSort(nodes))

	expect := []*NodeListStub{
		&NodeListStub{CreateIndex: 5},
		&NodeListStub{CreateIndex: 2},
		&NodeListStub{CreateIndex: 1},
	}
	if !reflect.DeepEqual(nodes, expect) {
		t.Fatalf("\n\n%#v\n\n%#v", nodes, expect)
	}
}
<commit_msg>Remove useless statement<commit_after>package api

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/hashicorp/nomad/testutil"
)

func TestNodes_List(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	var qm *QueryMeta
	var out []*NodeListStub
	var err error

	testutil.WaitForResult(func() (bool, error) {
		out, qm, err = nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Check that we got valid QueryMeta.
	assertQueryMeta(t, qm)
}

func TestNodes_PrefixList(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	var qm *QueryMeta
	var out []*NodeListStub
	var err error

	// Get the node ID
	var nodeID string
	testutil.WaitForResult(func() (bool, error) {
		out, _, err := nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		nodeID = out[0].ID
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Find node based on four character prefix
	out, qm, err = nodes.PrefixList(nodeID[:4])
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if n := len(out); n != 1 {
		t.Fatalf("expected 1 node, got: %d ", n)
	}

	// Check that we got valid QueryMeta.
	assertQueryMeta(t, qm)
}

func TestNodes_Info(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	// Retrieving a non-existent node returns error
	_, _, err := nodes.Info("12345678-abcd-efab-cdef-123456789abc", nil)
	if err == nil || !strings.Contains(err.Error(), "not found") {
		t.Fatalf("expected not found error, got: %#v", err)
	}

	// Get the node ID
	var nodeID, dc string
	testutil.WaitForResult(func() (bool, error) {
		out, _, err := nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		nodeID = out[0].ID
		dc = out[0].Datacenter
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Querying for existing nodes returns properly
	result, qm, err := nodes.Info(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	assertQueryMeta(t, qm)

	// Check that the result is what we expect
	if result.ID != nodeID || result.Datacenter != dc {
		t.Fatalf("expected %s (%s), got: %s (%s)",
			nodeID, dc,
			result.ID, result.Datacenter)
	}
}

func TestNodes_ToggleDrain(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	// Wait for node registration and get the ID
	var nodeID string
	testutil.WaitForResult(func() (bool, error) {
		out, _, err := nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		nodeID = out[0].ID
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Check for drain mode
	out, _, err := nodes.Info(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if out.Drain {
		t.Fatalf("drain mode should be off")
	}

	// Toggle it on
	wm, err := nodes.ToggleDrain(nodeID, true, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	assertWriteMeta(t, wm)

	// Check again
	out, _, err = nodes.Info(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if !out.Drain {
		t.Fatalf("drain mode should be on")
	}

	// Toggle off again
	wm, err = nodes.ToggleDrain(nodeID, false, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	assertWriteMeta(t, wm)

	// Check again
	out, _, err = nodes.Info(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if out.Drain {
		t.Fatalf("drain mode should be off")
	}
}

func TestNodes_Allocations(t *testing.T) {
	c, s := makeClient(t, nil, nil)
	defer s.Stop()
	nodes := c.Nodes()

	// Looking up by a non-existent node returns nothing. We
	// don't check the index here because it's possible the node
	// has already registered, in which case we will get a non-
	// zero result anyways.
	allocs, _, err := nodes.Allocations("nope", nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if n := len(allocs); n != 0 {
		t.Fatalf("expected 0 allocs, got: %d", n)
	}
}

func TestNodes_ForceEvaluate(t *testing.T) {
	c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
		c.DevMode = true
	})
	defer s.Stop()
	nodes := c.Nodes()

	// Force-eval on a non-existent node fails
	_, _, err := nodes.ForceEvaluate("12345678-abcd-efab-cdef-123456789abc", nil)
	if err == nil || !strings.Contains(err.Error(), "not found") {
		t.Fatalf("expected not found error, got: %#v", err)
	}

	// Wait for node registration and get the ID
	var nodeID string
	testutil.WaitForResult(func() (bool, error) {
		out, _, err := nodes.List(nil)
		if err != nil {
			return false, err
		}
		if n := len(out); n != 1 {
			return false, fmt.Errorf("expected 1 node, got: %d", n)
		}
		nodeID = out[0].ID
		return true, nil
	}, func(err error) {
		t.Fatalf("err: %s", err)
	})

	// Try force-eval again. We don't check the WriteMeta because
	// there are no allocations to process, so we would get an index
	// of zero. Same goes for the eval ID.
	_, _, err = nodes.ForceEvaluate(nodeID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestNodes_Sort(t *testing.T) {
	nodes := []*NodeListStub{
		&NodeListStub{CreateIndex: 2},
		&NodeListStub{CreateIndex: 1},
		&NodeListStub{CreateIndex: 5},
	}
	sort.Sort(NodeIndexSort(nodes))

	expect := []*NodeListStub{
		&NodeListStub{CreateIndex: 5},
		&NodeListStub{CreateIndex: 2},
		&NodeListStub{CreateIndex: 1},
	}
	if !reflect.DeepEqual(nodes, expect) {
		t.Fatalf("\n\n%#v\n\n%#v", nodes, expect)
	}
}
<|endoftext|>


<commit_before>package css
// package main

import (
  "fmt"
	"strings"
	"rubex"
)

type Lexeme int

const (
  SPACES = iota
  COMMA
  UNIVERSAL
  TYPE
  ELEMENT
  CLASS
  ID
  LBRACKET
  RBRACKET
  ATTR_NAME
  ATTR_VALUE
  EQUALS
  CONTAINS_CLASS
  DASH_PREFIXED
  STARTS_WITH
  ENDS_WITH
  CONTAINS
  MATCH_OP
  PSEUDO_CLASS
  FIRST_CHILD
  FIRST_OF_TYPE
  NTH_CHILD
  NTH_OF_TYPE
  ONLY_CHILD
  ONLY_OF_TYPE
  LAST_CHILD
  LAST_OF_TYPE
  NOT
  LPAREN
  RPAREN
  COEFFICIENT
  SIGNED
  UNSIGNED
  ODD
  EVEN
  N
  OPERATOR
  PLUS
  MINUS
  BINOMIAL
  ADJACENT_TO
  PRECEDES
  PARENT_OF
  ANCESTOR_OF
  // and a counter ... I can't believe I didn't think of this sooner
  NUM_LEXEMES
)

var pattern [NUM_LEXEMES]string

type Scope int

const (
  GLOBAL = iota
  LOCAL
)

func Convert(css string, scope Scope) string {
  matchers := allocate()
  defer deallocate(matchers)
  xpath, _ := selectors(matchers, []byte(css), scope)
  return xpath
}

func allocate() []*rubex.Regexp {
  // some overlap in here, but it'll make the parsing functions clearer
  pattern[SPACES] = `\s+`
  pattern[COMMA] = `\s*,`
  pattern[UNIVERSAL] = `\*`
  pattern[TYPE] = `[_a-zA-Z]\w*`
  pattern[ELEMENT] = `(\*|[_a-zA-Z]\w*)`
  pattern[CLASS] = `\.[-\w]+`
  pattern[ID] = `\#[-\w]+`
  pattern[LBRACKET] = `\[`
  pattern[RBRACKET] = `\]`
  pattern[ATTR_NAME] = `[-_:a-zA-Z][-\w:.]*`
  pattern[ATTR_VALUE] = `("(\\.|[^"\\])*"|'(\\.|[^'\\])*')`
  pattern[EQUALS] = `=`
  pattern[CONTAINS_CLASS] = `~=`
  pattern[DASH_PREFIXED] = `\|=`
  pattern[STARTS_WITH] = `\^=`
  pattern[ENDS_WITH] = `\$=`
  pattern[CONTAINS] = `\*=`
  pattern[MATCH_OP] = "(" + strings.Join([]string{pattern[EQUALS], pattern[CONTAINS_CLASS], pattern[DASH_PREFIXED], pattern[STARTS_WITH], pattern[ENDS_WITH], pattern[CONTAINS]}, "|") + ")"
  pattern[PSEUDO_CLASS] = `:[-a-z]+`
  pattern[FIRST_CHILD] = `:first-child`
  pattern[FIRST_OF_TYPE] = `:first-of-type`
  pattern[NTH_CHILD] = `:nth-child`
  pattern[NTH_OF_TYPE] = `:nth-of-type`
  pattern[ONLY_CHILD] = `:only-child`
  pattern[ONLY_OF_TYPE] = `:only-of-type`
  pattern[LAST_CHILD] = `:last-child`
  pattern[LAST_OF_TYPE] = `:last-of-type`
  pattern[NOT] = `:not`
  pattern[LPAREN] = `\s*\(`
  pattern[RPAREN] = `\s*\)`
  pattern[COEFFICIENT] = `[-+]?(\d+)?`
  pattern[SIGNED] = `[-+]?\d+`
  pattern[UNSIGNED] = `\d+`
  pattern[ODD] = `odd`
  pattern[EVEN] = `even`
  pattern[N] = `[nN]`
  pattern[OPERATOR] = `[-+]`
  pattern[PLUS] = `\+`
  pattern[MINUS] = `-`
  pattern[BINOMIAL] = strings.Join([]string{pattern[COEFFICIENT], pattern[N], `\s*`, pattern[OPERATOR], `\s*`, pattern[UNSIGNED]}, "")
  pattern[ADJACENT_TO] = `\s*\+`
  pattern[PRECEDES] = `\s*~`
  pattern[PARENT_OF] = `\s*>`
  pattern[ANCESTOR_OF] = `\s+`
  matchers := make([]*rubex.Regexp, 0, NUM_LEXEMES)
  for _, p := range pattern {
    matchers = append(matchers, rubex.MustCompile(`\A` + p))
  }
  return matchers
}

func deallocate(matchers []*rubex.Regexp) {
  for _, m := range matchers {
    m.Free()
  }
}

func selectors(matchers []*rubex.Regexp, input []byte, scope Scope) (string, []byte) {
  x, input := selector(matchers, input, scope)
  xs := []string{x}
  for peek(matchers, COMMA, input) {
    _, input = token(matchers, COMMA, input)
    x, input = selector(matchers, input, scope)
    xs = append(xs, x)
  }
  return strings.Join(xs, " | "), input
}

func selector(matchers []*rubex.Regexp, input []byte, scope Scope) (string, []byte) {
  var combinator Lexeme
  var xs []string
  if scope == LOCAL {
    xs = []string{"."}
  }
  if matched, remainder := token(matchers, PARENT_OF, input); matched != nil {
    combinator, input = PARENT_OF, remainder
  } else {
    combinator = ANCESTOR_OF
  }
  x, input := sequence(matchers, input, combinator)
  xs = append(xs, x)
  for {
    if matched, remainder := token(matchers, ADJACENT_TO, input); matched != nil {
      combinator, input = ADJACENT_TO, remainder
    } else if matched, remainder := token(matchers, PRECEDES, input); matched != nil {
      combinator, input = PRECEDES, remainder
    } else if matched, remainder := token(matchers, PARENT_OF, input); matched != nil {
      combinator, input = PARENT_OF, remainder
    } else if matched, remainder := token(matchers, ANCESTOR_OF, input); matched != nil {
      combinator, input = ANCESTOR_OF, remainder
    } else {
      break
    }
    x, input = sequence(matchers, input, combinator)
    xs = append(xs, x)
  }
  return strings.Join(xs, ""), input
}

func sequence(matchers []*rubex.Regexp, input []byte, combinator Lexeme) (string, []byte) {
  _, input = token(matchers, SPACES, input)
  x, ps := "", []string{}

  switch combinator {
  case ANCESTOR_OF:
    x = "/descendant-or-self::*/*"
  case PARENT_OF:
    x = "/child::*"
  case PRECEDES:
    x = "/following-sibling::*"
  case ADJACENT_TO:
    x = "/following-sibling::*"
    ps = append(ps, "position()=1")
  }

  if e, remainder := token(matchers, ELEMENT, input); e != nil {
    input = remainder
    if len(ps) > 0 {
      ps = append(ps, " and ")
    }
    ps = append(ps, "self::"+string(e))
    if !(peek(matchers, ID, input) || peek(matchers, CLASS, input) || peek(matchers, PSEUDO_CLASS, input) || peek(matchers, LBRACKET, input)) {
      pstr := strings.Join(ps, "")
      if pstr != "" {
        pstr = fmt.Sprintf("[%s]", pstr)
      }
      return x + pstr, input
    }
  }
  q, input, connective := qualifier(matchers, input)
  if q == "" {
    panic("Invalid CSS selector")
  }
  if len(ps) > 0 {
    ps = append(ps, connective)
  }
  ps = append(ps, q)
  for q, r, c := qualifier(matchers, input); q != ""; q, r, c = qualifier(matchers, input) {
    ps, input = append(ps, c, q), r
  }
  pstr := strings.Join(ps, "")
  if combinator != NOT {
    pstr = fmt.Sprintf("[%s]", pstr)
  }
  return x + pstr, input
}

func qualifier(matchers []*rubex.Regexp, input []byte) (string, []byte, string) {
  p, connective := "", ""
  if t, remainder := token(matchers, CLASS, input); t != nil {
    p = fmt.Sprintf(`contains(concat(" ", @class, " "), " %s ")`, string(t[1:]))
    input = remainder
    connective = " and "
  } else if t, remainder := token(matchers, ID, input); t != nil {
    p, input, connective = fmt.Sprintf(`@id="%s"`, string(t[1:])), remainder, " and "
  } else if peek(matchers, PSEUDO_CLASS, input) {
    p, input, connective = pseudoClass(matchers, input)
  } else if peek(matchers, LBRACKET, input) {
    p, input = attribute(matchers, input)
    connective = " and "
  }
  return p, input, connective
}

func pseudoClass(matchers []*rubex.Regexp, input []byte) (string, []byte, string) {
  class, input := token(matchers, PSEUDO_CLASS, input)
  var p, connective string
  switch string(class) {
  case ":first-child":
    p, connective = "position()=1", " and "
  case ":first-of-type":
    p, connective = "position()=1", "]["
  case ":last-child":
    p, connective = "position()=last()", " and "
  case ":last-of-type":
    p, connective = "position()=last()", "]["
  case ":only-child":
    p, connective = "position() = 1 and position() = last()", " and "
  case ":only-of-type":
    p, connective = "position() = 1 and position() = last()", "]["
  case ":nth-child":
    p, input = nth(matchers, input)
    connective = " and "
  case ":nth-of-type":
    p, input = nth(matchers, input)
    connective = "]["
  case ":not":
    p, input = negate(matchers, input)
    connective = " and "
  default:
    panic(`Cannot convert CSS pseudo-class "` + string(class) + `" to XPath.`)
  }
  return p, input, connective
}

func nth(matchers []*rubex.Regexp, input []byte) (string, []byte) {
  lparen, input := token(matchers, LPAREN, input)
  if lparen == nil {
    panic(":nth-child and :nth-of-type require an parenthesized argument")
  }
  _, input = token(matchers, SPACES, input)
  var expr string
  if e, rem := token(matchers, EVEN, input); e != nil {
    expr, input = "position() mod 2 = 0", rem
  } else if e, rem := token(matchers, ODD, input); e != nil {
    expr, input = "position() mod 2 = 1", rem
  } else if e, _ := token(matchers, BINOMIAL, input); e != nil {
    var coefficient, operator, constant []byte
    coefficient, input = token(matchers, COEFFICIENT, input)
    switch string(coefficient) {
    case "", "+":
      coefficient = []byte("1")
    case "-":
      coefficient = []byte("-1")
    }
    _, input = token(matchers, N, input)
    _, input = token(matchers, SPACES, input)
    operator, input = token(matchers, OPERATOR, input)
    _, input = token(matchers, SPACES, input)
    constant, input = token(matchers, UNSIGNED, input)
    expr = fmt.Sprintf("(position() %s %s) mod %s = 0", invert(string(operator)), string(constant), string(coefficient))
  } else if e, rem := token(matchers, SIGNED, input); e != nil {
    expr, input = "position() = "+string(e), rem
  } else {
    panic("Invalid argument to :nth-child or :nth-of-type.")
  }
  fmt.Println(string(input))
  _, input = token(matchers, SPACES, input)
  rparen, input := token(matchers, RPAREN, input)
  if rparen == nil {
    panic("Unterminated argument to :nth-child or :nth-of-type.")
  }
  return expr, input
}

func invert(op string) string {
  op = strings.TrimSpace(op)
  if op == "+" {
    op = "-"
  } else {
    op = "+"
  }
  return op
}

func negate(matchers []*rubex.Regexp, input []byte) (string, []byte) {
  _, input = token(matchers, SPACES, input)
  lparen, input := token(matchers, LPAREN, input)
  if lparen == nil {
    panic(":not requires a parenthesized argument.")
  }
  _, input = token(matchers, SPACES, input)
  p, input := sequence(matchers, input, NOT)
  _, input = token(matchers, SPACES, input)
  rparen, input := token(matchers, RPAREN, input)
  if rparen == nil {
    panic("Unterminated argument to :not.")
  }
  return fmt.Sprintf("not(%s)", p), input
}

func attribute(matchers []*rubex.Regexp, input []byte) (string, []byte) {
  _, input = token(matchers, LBRACKET, input)
  _, input = token(matchers, SPACES, input)
  name, input := token(matchers, ATTR_NAME, input)
  if name == nil {
    panic("Attribute selector requires an attribute name.")
  }
  _, input = token(matchers, SPACES, input)
  if rbracket, remainder := token(matchers, RBRACKET, input); rbracket != nil {
    return "@" + string(name), remainder
  }
  op, input := token(matchers, MATCH_OP, input)
  if op == nil {
    panic("Missing operator in attribute selector.")
  }
  _, input = token(matchers, SPACES, input)
  val, input := token(matchers, ATTR_VALUE, input)
  if val == nil {
    panic("Missing value in attribute selector.")
  }
  _, input = token(matchers, SPACES, input)
  rbracket, input := token(matchers, RBRACKET, input)
  if rbracket == nil {
    panic("Unterminated attribute selector.")
  }
  var expr string
  n, v := string(name), string(val)
  switch string(op) {
  case "=":
    expr = fmt.Sprintf("@%s=%s", n, v)
  case "~=":
    expr = fmt.Sprintf(`contains(concat(" ", @%s, " "), concat(" ", %s, " "))`, n, v)
  case "|=":
    expr = fmt.Sprintf(`(@%s=%s or starts-with(@%s, concat(%s, "-")))`, n, v, n, v)
  case "^=":
    expr = fmt.Sprintf("starts-with(@%s, %s)", n, v)
  case "$=":
    // oy, libxml doesn't support ends-with
    // generate something like: div[substring(@class, string-length(@class) - string-length('foo') + 1) = 'foo']
    expr = fmt.Sprintf("substring(@%s, string-length(@%s) - string-length(%s) + 1) = %s", n, n, v, v)
  case "*=":
    expr = fmt.Sprintf("contains(@%s, %s)", n, v)
  }
  return expr, input
}

func token(matchers []*rubex.Regexp, lexeme Lexeme, input []byte) ([]byte, []byte) {
  matched := matchers[lexeme].Find(input)
  length := len(matched)
  if length == 0 {
    matched = nil
  }
  return matched, input[length:]
}

func peek(matchers []*rubex.Regexp, lexeme Lexeme, input []byte) bool {
  matched, _ := token(matchers, lexeme, input)
  return matched != nil
}
<commit_msg>move string array init out<commit_after>package css
// package main

import (
  "fmt"
	"strings"
	"rubex"
)

type Lexeme int

const (
  SPACES = iota
  COMMA
  UNIVERSAL
  TYPE
  ELEMENT
  CLASS
  ID
  LBRACKET
  RBRACKET
  ATTR_NAME
  ATTR_VALUE
  EQUALS
  CONTAINS_CLASS
  DASH_PREFIXED
  STARTS_WITH
  ENDS_WITH
  CONTAINS
  MATCH_OP
  PSEUDO_CLASS
  FIRST_CHILD
  FIRST_OF_TYPE
  NTH_CHILD
  NTH_OF_TYPE
  ONLY_CHILD
  ONLY_OF_TYPE
  LAST_CHILD
  LAST_OF_TYPE
  NOT
  LPAREN
  RPAREN
  COEFFICIENT
  SIGNED
  UNSIGNED
  ODD
  EVEN
  N
  OPERATOR
  PLUS
  MINUS
  BINOMIAL
  ADJACENT_TO
  PRECEDES
  PARENT_OF
  ANCESTOR_OF
  // and a counter ... I can't believe I didn't think of this sooner
  NUM_LEXEMES
)

var pattern [NUM_LEXEMES]string

func init() {
    pattern[SPACES] = `\s+`
    pattern[COMMA] = `\s*,`
    pattern[UNIVERSAL] = `\*`
    pattern[TYPE] = `[_a-zA-Z]\w*`
    pattern[ELEMENT] = `(\*|[_a-zA-Z]\w*)`
    pattern[CLASS] = `\.[-\w]+`
    pattern[ID] = `\#[-\w]+`
    pattern[LBRACKET] = `\[`
    pattern[RBRACKET] = `\]`
    pattern[ATTR_NAME] = `[-_:a-zA-Z][-\w:.]*`
    pattern[ATTR_VALUE] = `("(\\.|[^"\\])*"|'(\\.|[^'\\])*')`
    pattern[EQUALS] = `=`
    pattern[CONTAINS_CLASS] = `~=`
    pattern[DASH_PREFIXED] = `\|=`
    pattern[STARTS_WITH] = `\^=`
    pattern[ENDS_WITH] = `\$=`
    pattern[CONTAINS] = `\*=`
    pattern[MATCH_OP] = "(" + strings.Join([]string{pattern[EQUALS], pattern[CONTAINS_CLASS], pattern[DASH_PREFIXED], pattern[STARTS_WITH], pattern[ENDS_WITH], pattern[CONTAINS]}, "|") + ")"
    pattern[PSEUDO_CLASS] = `:[-a-z]+`
    pattern[FIRST_CHILD] = `:first-child`
    pattern[FIRST_OF_TYPE] = `:first-of-type`
    pattern[NTH_CHILD] = `:nth-child`
    pattern[NTH_OF_TYPE] = `:nth-of-type`
    pattern[ONLY_CHILD] = `:only-child`
    pattern[ONLY_OF_TYPE] = `:only-of-type`
    pattern[LAST_CHILD] = `:last-child`
    pattern[LAST_OF_TYPE] = `:last-of-type`
    pattern[NOT] = `:not`
    pattern[LPAREN] = `\s*\(`
    pattern[RPAREN] = `\s*\)`
    pattern[COEFFICIENT] = `[-+]?(\d+)?`
    pattern[SIGNED] = `[-+]?\d+`
    pattern[UNSIGNED] = `\d+`
    pattern[ODD] = `odd`
    pattern[EVEN] = `even`
    pattern[N] = `[nN]`
    pattern[OPERATOR] = `[-+]`
    pattern[PLUS] = `\+`
    pattern[MINUS] = `-`
    pattern[BINOMIAL] = strings.Join([]string{pattern[COEFFICIENT], pattern[N], `\s*`, pattern[OPERATOR], `\s*`, pattern[UNSIGNED]}, "")
    pattern[ADJACENT_TO] = `\s*\+`
    pattern[PRECEDES] = `\s*~`
    pattern[PARENT_OF] = `\s*>`
    pattern[ANCESTOR_OF] = `\s+`
}

type Scope int

const (
  GLOBAL = iota
  LOCAL
)

func Convert(css string, scope Scope) string {
  matchers := allocate()
  defer deallocate(matchers)
  xpath, _ := selectors(matchers, []byte(css), scope)
  return xpath
}

func allocate() []*rubex.Regexp {
  // some overlap in here, but it'll make the parsing functions clearer
  matchers := make([]*rubex.Regexp, 0, NUM_LEXEMES)
  for _, p := range pattern {
    matchers = append(matchers, rubex.MustCompile(`\A` + p))
  }
  return matchers
}

func deallocate(matchers []*rubex.Regexp) {
  for _, m := range matchers {
    m.Free()
  }
}

func selectors(matchers []*rubex.Regexp, input []byte, scope Scope) (string, []byte) {
  x, input := selector(matchers, input, scope)
  xs := []string{x}
  for peek(matchers, COMMA, input) {
    _, input = token(matchers, COMMA, input)
    x, input = selector(matchers, input, scope)
    xs = append(xs, x)
  }
  return strings.Join(xs, " | "), input
}

func selector(matchers []*rubex.Regexp, input []byte, scope Scope) (string, []byte) {
  var combinator Lexeme
  var xs []string
  if scope == LOCAL {
    xs = []string{"."}
  }
  if matched, remainder := token(matchers, PARENT_OF, input); matched != nil {
    combinator, input = PARENT_OF, remainder
  } else {
    combinator = ANCESTOR_OF
  }
  x, input := sequence(matchers, input, combinator)
  xs = append(xs, x)
  for {
    if matched, remainder := token(matchers, ADJACENT_TO, input); matched != nil {
      combinator, input = ADJACENT_TO, remainder
    } else if matched, remainder := token(matchers, PRECEDES, input); matched != nil {
      combinator, input = PRECEDES, remainder
    } else if matched, remainder := token(matchers, PARENT_OF, input); matched != nil {
      combinator, input = PARENT_OF, remainder
    } else if matched, remainder := token(matchers, ANCESTOR_OF, input); matched != nil {
      combinator, input = ANCESTOR_OF, remainder
    } else {
      break
    }
    x, input = sequence(matchers, input, combinator)
    xs = append(xs, x)
  }
  return strings.Join(xs, ""), input
}

func sequence(matchers []*rubex.Regexp, input []byte, combinator Lexeme) (string, []byte) {
  _, input = token(matchers, SPACES, input)
  x, ps := "", []string{}

  switch combinator {
  case ANCESTOR_OF:
    x = "/descendant-or-self::*/*"
  case PARENT_OF:
    x = "/child::*"
  case PRECEDES:
    x = "/following-sibling::*"
  case ADJACENT_TO:
    x = "/following-sibling::*"
    ps = append(ps, "position()=1")
  }

  if e, remainder := token(matchers, ELEMENT, input); e != nil {
    input = remainder
    if len(ps) > 0 {
      ps = append(ps, " and ")
    }
    ps = append(ps, "self::"+string(e))
    if !(peek(matchers, ID, input) || peek(matchers, CLASS, input) || peek(matchers, PSEUDO_CLASS, input) || peek(matchers, LBRACKET, input)) {
      pstr := strings.Join(ps, "")
      if pstr != "" {
        pstr = fmt.Sprintf("[%s]", pstr)
      }
      return x + pstr, input
    }
  }
  q, input, connective := qualifier(matchers, input)
  if q == "" {
    panic("Invalid CSS selector")
  }
  if len(ps) > 0 {
    ps = append(ps, connective)
  }
  ps = append(ps, q)
  for q, r, c := qualifier(matchers, input); q != ""; q, r, c = qualifier(matchers, input) {
    ps, input = append(ps, c, q), r
  }
  pstr := strings.Join(ps, "")
  if combinator != NOT {
    pstr = fmt.Sprintf("[%s]", pstr)
  }
  return x + pstr, input
}

func qualifier(matchers []*rubex.Regexp, input []byte) (string, []byte, string) {
  p, connective := "", ""
  if t, remainder := token(matchers, CLASS, input); t != nil {
    p = fmt.Sprintf(`contains(concat(" ", @class, " "), " %s ")`, string(t[1:]))
    input = remainder
    connective = " and "
  } else if t, remainder := token(matchers, ID, input); t != nil {
    p, input, connective = fmt.Sprintf(`@id="%s"`, string(t[1:])), remainder, " and "
  } else if peek(matchers, PSEUDO_CLASS, input) {
    p, input, connective = pseudoClass(matchers, input)
  } else if peek(matchers, LBRACKET, input) {
    p, input = attribute(matchers, input)
    connective = " and "
  }
  return p, input, connective
}

func pseudoClass(matchers []*rubex.Regexp, input []byte) (string, []byte, string) {
  class, input := token(matchers, PSEUDO_CLASS, input)
  var p, connective string
  switch string(class) {
  case ":first-child":
    p, connective = "position()=1", " and "
  case ":first-of-type":
    p, connective = "position()=1", "]["
  case ":last-child":
    p, connective = "position()=last()", " and "
  case ":last-of-type":
    p, connective = "position()=last()", "]["
  case ":only-child":
    p, connective = "position() = 1 and position() = last()", " and "
  case ":only-of-type":
    p, connective = "position() = 1 and position() = last()", "]["
  case ":nth-child":
    p, input = nth(matchers, input)
    connective = " and "
  case ":nth-of-type":
    p, input = nth(matchers, input)
    connective = "]["
  case ":not":
    p, input = negate(matchers, input)
    connective = " and "
  default:
    panic(`Cannot convert CSS pseudo-class "` + string(class) + `" to XPath.`)
  }
  return p, input, connective
}

func nth(matchers []*rubex.Regexp, input []byte) (string, []byte) {
  lparen, input := token(matchers, LPAREN, input)
  if lparen == nil {
    panic(":nth-child and :nth-of-type require an parenthesized argument")
  }
  _, input = token(matchers, SPACES, input)
  var expr string
  if e, rem := token(matchers, EVEN, input); e != nil {
    expr, input = "position() mod 2 = 0", rem
  } else if e, rem := token(matchers, ODD, input); e != nil {
    expr, input = "position() mod 2 = 1", rem
  } else if e, _ := token(matchers, BINOMIAL, input); e != nil {
    var coefficient, operator, constant []byte
    coefficient, input = token(matchers, COEFFICIENT, input)
    switch string(coefficient) {
    case "", "+":
      coefficient = []byte("1")
    case "-":
      coefficient = []byte("-1")
    }
    _, input = token(matchers, N, input)
    _, input = token(matchers, SPACES, input)
    operator, input = token(matchers, OPERATOR, input)
    _, input = token(matchers, SPACES, input)
    constant, input = token(matchers, UNSIGNED, input)
    expr = fmt.Sprintf("(position() %s %s) mod %s = 0", invert(string(operator)), string(constant), string(coefficient))
  } else if e, rem := token(matchers, SIGNED, input); e != nil {
    expr, input = "position() = "+string(e), rem
  } else {
    panic("Invalid argument to :nth-child or :nth-of-type.")
  }
  fmt.Println(string(input))
  _, input = token(matchers, SPACES, input)
  rparen, input := token(matchers, RPAREN, input)
  if rparen == nil {
    panic("Unterminated argument to :nth-child or :nth-of-type.")
  }
  return expr, input
}

func invert(op string) string {
  op = strings.TrimSpace(op)
  if op == "+" {
    op = "-"
  } else {
    op = "+"
  }
  return op
}

func negate(matchers []*rubex.Regexp, input []byte) (string, []byte) {
  _, input = token(matchers, SPACES, input)
  lparen, input := token(matchers, LPAREN, input)
  if lparen == nil {
    panic(":not requires a parenthesized argument.")
  }
  _, input = token(matchers, SPACES, input)
  p, input := sequence(matchers, input, NOT)
  _, input = token(matchers, SPACES, input)
  rparen, input := token(matchers, RPAREN, input)
  if rparen == nil {
    panic("Unterminated argument to :not.")
  }
  return fmt.Sprintf("not(%s)", p), input
}

func attribute(matchers []*rubex.Regexp, input []byte) (string, []byte) {
  _, input = token(matchers, LBRACKET, input)
  _, input = token(matchers, SPACES, input)
  name, input := token(matchers, ATTR_NAME, input)
  if name == nil {
    panic("Attribute selector requires an attribute name.")
  }
  _, input = token(matchers, SPACES, input)
  if rbracket, remainder := token(matchers, RBRACKET, input); rbracket != nil {
    return "@" + string(name), remainder
  }
  op, input := token(matchers, MATCH_OP, input)
  if op == nil {
    panic("Missing operator in attribute selector.")
  }
  _, input = token(matchers, SPACES, input)
  val, input := token(matchers, ATTR_VALUE, input)
  if val == nil {
    panic("Missing value in attribute selector.")
  }
  _, input = token(matchers, SPACES, input)
  rbracket, input := token(matchers, RBRACKET, input)
  if rbracket == nil {
    panic("Unterminated attribute selector.")
  }
  var expr string
  n, v := string(name), string(val)
  switch string(op) {
  case "=":
    expr = fmt.Sprintf("@%s=%s", n, v)
  case "~=":
    expr = fmt.Sprintf(`contains(concat(" ", @%s, " "), concat(" ", %s, " "))`, n, v)
  case "|=":
    expr = fmt.Sprintf(`(@%s=%s or starts-with(@%s, concat(%s, "-")))`, n, v, n, v)
  case "^=":
    expr = fmt.Sprintf("starts-with(@%s, %s)", n, v)
  case "$=":
    // oy, libxml doesn't support ends-with
    // generate something like: div[substring(@class, string-length(@class) - string-length('foo') + 1) = 'foo']
    expr = fmt.Sprintf("substring(@%s, string-length(@%s) - string-length(%s) + 1) = %s", n, n, v, v)
  case "*=":
    expr = fmt.Sprintf("contains(@%s, %s)", n, v)
  }
  return expr, input
}

func token(matchers []*rubex.Regexp, lexeme Lexeme, input []byte) ([]byte, []byte) {
  matched := matchers[lexeme].Find(input)
  length := len(matched)
  if length == 0 {
    matched = nil
  }
  return matched, input[length:]
}

func peek(matchers []*rubex.Regexp, lexeme Lexeme, input []byte) bool {
  matched, _ := token(matchers, lexeme, input)
  return matched != nil
}
<|endoftext|>