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

package service

import (
	"bufio"
	"fmt"
	"io"
	"sync/atomic"
	"runtime"
//"github.com/surgemq/message"
	"encoding/binary"
	"strconv"
)

var (
	bufcnt int64
	DefaultBufferSize int64

	DeviceInBufferSize int64
	DeviceOutBufferSize int64

	MasterInBufferSize int64
	MasterOutBufferSize int64
)

const (
	smallReadBlockSize = 512
	defaultReadBlockSize = 8192
	defaultWriteBlockSize = 8192
)

/**
2016.03.03 修改
bingbuffer结构体
 */
type buffer struct {
	readIndex  int64        //读序号
	writeIndex int64        //写序号
	ringBuffer []*ByteArray //环形buffer指针数组
	bufferSize int64        //初始化环形buffer指针数组大小
	mask       int64        //掩码：bufferSize-1
	done       int64        //是否完成
}

type ByteArray struct {
	bArray []byte
}

func (this *ByteArray)GetArray() ([]byte) {
	return this.bArray
}

/**
2016.03.03 添加
初始化ringbuffer
参数bufferSize：初始化环形buffer指针数组大小
 */
func newBuffer(size int64) (*buffer, error) {
	if size < 0 {
		return nil, bufio.ErrNegativeCount
	}
	if size == 0 {
		size = DefaultBufferSize
	}
	if !powerOfTwo64(size) {
		fmt.Printf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
		return nil, fmt.Errorf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
	}

	return &buffer{
		readIndex: int64(0), //读序号
		writeIndex: int64(0), //写序号
		ringBuffer: make([]*ByteArray, size), //环形buffer指针数组
		bufferSize: size, //初始化环形buffer指针数组大小
		mask:size - 1,
	}, nil
}

/**
2016.03.03 添加
获取当前读序号
 */
func (this *buffer)GetCurrentReadIndex() (int64) {
	return atomic.LoadInt64(&this.readIndex)
}
/**
2016.03.03 添加
获取当前写序号
 */
func (this *buffer)GetCurrentWriteIndex() (int64) {
	return atomic.LoadInt64(&this.writeIndex)
}

/**
2016.03.03 添加
读取ringbuffer指定的buffer指针，返回该指针并清空ringbuffer该位置存在的指针内容，以及将读序号加1
 */
func (this *buffer)ReadBuffer() (p *[]byte, ok bool) {
	ok = true
	p = nil

	readIndex := atomic.LoadInt64(&this.readIndex)
	writeIndex := atomic.LoadInt64(&this.writeIndex)
	switch  {
	case readIndex >= writeIndex:
		ok = false
	case writeIndex - readIndex > this.bufferSize:
		ok = false
	default:
		//index := buffer.readIndex % buffer.bufferSize
		index := readIndex & this.mask

		p_ := ByteArray{}(this.ringBuffer[index])
		this.ringBuffer[index] = nil
		atomic.AddInt64(&this.readIndex, 1)
		p = p_.GetArray()[0:]
		if p == nil {
			ok = false
		}
	}
	return p, ok
}

/**
2016.03.03 添加
写入ringbuffer指针，以及将写序号加1
 */
func (this *buffer)WriteBuffer(in []byte) (ok bool) {
	ok = true

	readIndex := atomic.LoadInt64(&this.readIndex)
	writeIndex := atomic.LoadInt64(&this.writeIndex)
	switch  {
	case writeIndex - readIndex < 0:
		ok = false
	default:
		//index := buffer.writeIndex % buffer.bufferSize
		index := writeIndex & this.mask
		if this.ringBuffer[index] == nil {
			this.ringBuffer[index] = &ByteArray{bArray:in}
			atomic.AddInt64(&this.writeIndex, 1)
		}else {
			ok = false
		}
	}
	return ok
}

/**
2016.03.03 修改
关闭缓存
 */
func (this *buffer) Close() error {
	atomic.StoreInt64(&this.done, 1)
	return nil
}
/*

/**
2016.03.03 修改
向ringbuffer中写数据（从connection的中向ringbuffer中写）--生产者
*/
func (this *buffer) ReadFrom(r io.Reader) (int64, error) {
	defer this.Close()
	for {
		total := int64(0)
		if this.isDone() {
			return total, io.EOF
		}
		b := make([]byte, 5)
		n, err := r.Read(b[0:1])

		if n > 0 {
			total += int64(n)
			if err != nil {
				return total, err
			}
		}

		/**************************/
		cnt := 1

		// Let's read enough bytes to get the message header (msg type, remaining length)
		for {
			// If we have read 5 bytes and still not done, then there's a problem.
			if cnt > 4 {
				return 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set")
			}

			// Peek cnt bytes from the input buffer.
			_, err := r.Read(b[cnt:cnt + 1])
			if err != nil {
				return 0, err
			}

			// If we got enough bytes, then check the last byte to see if the continuation
			// bit is set. If so, increment cnt and continue peeking
			if b[cnt] >= 0x80 {
				cnt++
			} else {
				break
			}
		}

		// Get the remaining length of the message
		remlen, _ := binary.Uvarint(b[1:])

		// Total message length is remlen + 1 (msg type) + m (remlen bytes)
		len := int64(len(b))
		remlen_ := int64(remlen)
		total = remlen_ + int64(len)

		//mtype := message.MessageType(b[0] >> 4)
		/****************/
		//var msg message.Message
		//
		//msg, err = mtype.New()
		//if err != nil {
		//	return 0, err
		//}
		b_ := make([]byte, remlen_)
		_, err = r.Read(b_[0:])
		if err != nil {
			return 0, err
		}

		b = append(b, b_...)
		//n, err = msg.Decode(b)
		//if err != nil {
		//	return 0, err
		//}

		/*************************/

		for !this.WriteBuffer(&b) {
			runtime.Gosched()
		}

		return total, nil
	}
}

/**
2016.03.03 修改
 */
func (this *buffer) WriteTo(w io.Writer) (int64, error) {
	defer this.Close()
	total := int64(0)
	for {
		if this.isDone() {
			return total, io.EOF
		}
		p, ok := this.ReadBuffer()
		if !ok {
			runtime.Gosched()
		}
		Log.Debugc(func() string {
			return fmt.Sprintf("p=" + strconv.FormatBool(p == nil))
		})
		Log.Debugc(func() string {
			return fmt.Sprintf("WriteTo函数》》读取*p：" + string(*p))
		})

		Log.Debugc(func() string {
			return fmt.Sprintf(" WriteTo(w io.Writer)(7)")
		})
		//
		//Log.Errorc(func() string {
		//	return fmt.Sprintf("msg::" + msg.Name())
		//})
		//
		//p := make([]byte, msg.Len())
		//_, err := msg.Encode(p)
		//if err != nil {
		//	Log.Errorc(func() string {
		//		return fmt.Sprintf("msg.Encode(p)")
		//	})
		//	return total, io.EOF
		//}
		// There's some data, let's process it first
		if len(*p) > 0 {
			n, err := w.Write(*p)
			total += int64(n)
			Log.Debugc(func() string {
				return fmt.Sprintf("Wrote %d bytes, totaling %d bytes", n, total)
			})

			if err != nil {
				Log.Errorc(func() string {
					return fmt.Sprintf("w.Write(p) error")
				})
				return total, err
			}
		}

		return total, nil
	}
}

/**
2016.03.03 修改
*/
func (this *buffer) isDone() bool {
	if atomic.LoadInt64(&this.done) == 1 {
		return true
	}

	return false
}

func powerOfTwo64(n int64) bool {
	return n != 0 && (n & (n - 1)) == 0
}

func roundUpPowerOfTwo64(n int64) int64 {
	n--
	n |= n >> 1
	n |= n >> 2
	n |= n >> 4
	n |= n >> 8
	n |= n >> 16
	n |= n >> 32
	n++

	return n
}
<commit_msg>添加测试日志<commit_after>// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package service

import (
	"bufio"
	"fmt"
	"io"
	"sync/atomic"
	"runtime"
//"github.com/surgemq/message"
	"encoding/binary"
	"strconv"
)

var (
	bufcnt int64
	DefaultBufferSize int64

	DeviceInBufferSize int64
	DeviceOutBufferSize int64

	MasterInBufferSize int64
	MasterOutBufferSize int64
)

const (
	smallReadBlockSize = 512
	defaultReadBlockSize = 8192
	defaultWriteBlockSize = 8192
)

/**
2016.03.03 修改
bingbuffer结构体
 */
type buffer struct {
	readIndex  int64        //读序号
	writeIndex int64        //写序号
	ringBuffer []*ByteArray //环形buffer指针数组
	bufferSize int64        //初始化环形buffer指针数组大小
	mask       int64        //掩码：bufferSize-1
	done       int64        //是否完成
}

type ByteArray struct {
	bArray []byte
}

func (this *ByteArray)GetArray() ([]byte) {
	return this.bArray
}

/**
2016.03.03 添加
初始化ringbuffer
参数bufferSize：初始化环形buffer指针数组大小
 */
func newBuffer(size int64) (*buffer, error) {
	if size < 0 {
		return nil, bufio.ErrNegativeCount
	}
	if size == 0 {
		size = DefaultBufferSize
	}
	if !powerOfTwo64(size) {
		fmt.Printf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
		return nil, fmt.Errorf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
	}

	return &buffer{
		readIndex: int64(0), //读序号
		writeIndex: int64(0), //写序号
		ringBuffer: make([]*ByteArray, size), //环形buffer指针数组
		bufferSize: size, //初始化环形buffer指针数组大小
		mask:size - 1,
	}, nil
}

/**
2016.03.03 添加
获取当前读序号
 */
func (this *buffer)GetCurrentReadIndex() (int64) {
	return atomic.LoadInt64(&this.readIndex)
}
/**
2016.03.03 添加
获取当前写序号
 */
func (this *buffer)GetCurrentWriteIndex() (int64) {
	return atomic.LoadInt64(&this.writeIndex)
}

/**
2016.03.03 添加
读取ringbuffer指定的buffer指针，返回该指针并清空ringbuffer该位置存在的指针内容，以及将读序号加1
 */
func (this *buffer)ReadBuffer() (p *[]byte, ok bool) {
	ok = true
	p = nil

	readIndex := atomic.LoadInt64(&this.readIndex)
	writeIndex := atomic.LoadInt64(&this.writeIndex)
	switch  {
	case readIndex >= writeIndex:
		ok = false
	case writeIndex - readIndex > this.bufferSize:
		ok = false
	default:
		//index := buffer.readIndex % buffer.bufferSize
		index := readIndex & this.mask

		p_ := *(this.ringBuffer[index])
		this.ringBuffer[index] = nil
		atomic.AddInt64(&this.readIndex, 1)
		p = p_.GetArray()[0:]
		if p == nil {
			ok = false
		}
	}
	return p, ok
}

/**
2016.03.03 添加
写入ringbuffer指针，以及将写序号加1
 */
func (this *buffer)WriteBuffer(in []byte) (ok bool) {
	ok = true

	readIndex := atomic.LoadInt64(&this.readIndex)
	writeIndex := atomic.LoadInt64(&this.writeIndex)
	switch  {
	case writeIndex - readIndex < 0:
		ok = false
	default:
		//index := buffer.writeIndex % buffer.bufferSize
		index := writeIndex & this.mask
		if this.ringBuffer[index] == nil {
			this.ringBuffer[index] = &ByteArray{bArray:in}
			atomic.AddInt64(&this.writeIndex, 1)
		}else {
			ok = false
		}
	}
	return ok
}

/**
2016.03.03 修改
关闭缓存
 */
func (this *buffer) Close() error {
	atomic.StoreInt64(&this.done, 1)
	return nil
}
/*

/**
2016.03.03 修改
向ringbuffer中写数据（从connection的中向ringbuffer中写）--生产者
*/
func (this *buffer) ReadFrom(r io.Reader) (int64, error) {
	defer this.Close()
	for {
		total := int64(0)
		if this.isDone() {
			return total, io.EOF
		}
		b := make([]byte, 5)
		n, err := r.Read(b[0:1])

		if n > 0 {
			total += int64(n)
			if err != nil {
				return total, err
			}
		}

		/**************************/
		cnt := 1

		// Let's read enough bytes to get the message header (msg type, remaining length)
		for {
			// If we have read 5 bytes and still not done, then there's a problem.
			if cnt > 4 {
				return 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set")
			}

			// Peek cnt bytes from the input buffer.
			_, err := r.Read(b[cnt:cnt + 1])
			if err != nil {
				return 0, err
			}

			// If we got enough bytes, then check the last byte to see if the continuation
			// bit is set. If so, increment cnt and continue peeking
			if b[cnt] >= 0x80 {
				cnt++
			} else {
				break
			}
		}

		// Get the remaining length of the message
		remlen, _ := binary.Uvarint(b[1:])

		// Total message length is remlen + 1 (msg type) + m (remlen bytes)
		len := int64(len(b))
		remlen_ := int64(remlen)
		total = remlen_ + int64(len)

		//mtype := message.MessageType(b[0] >> 4)
		/****************/
		//var msg message.Message
		//
		//msg, err = mtype.New()
		//if err != nil {
		//	return 0, err
		//}
		b_ := make([]byte, remlen_)
		_, err = r.Read(b_[0:])
		if err != nil {
			return 0, err
		}

		b = append(b, b_...)
		//n, err = msg.Decode(b)
		//if err != nil {
		//	return 0, err
		//}

		/*************************/

		for !this.WriteBuffer(&b) {
			runtime.Gosched()
		}

		return total, nil
	}
}

/**
2016.03.03 修改
 */
func (this *buffer) WriteTo(w io.Writer) (int64, error) {
	defer this.Close()
	total := int64(0)
	for {
		if this.isDone() {
			return total, io.EOF
		}
		p, ok := this.ReadBuffer()
		if !ok {
			runtime.Gosched()
		}
		Log.Debugc(func() string {
			return fmt.Sprintf("p=" + strconv.FormatBool(p == nil))
		})
		Log.Debugc(func() string {
			return fmt.Sprintf("WriteTo函数》》读取*p：" + string(*p))
		})

		Log.Debugc(func() string {
			return fmt.Sprintf(" WriteTo(w io.Writer)(7)")
		})
		//
		//Log.Errorc(func() string {
		//	return fmt.Sprintf("msg::" + msg.Name())
		//})
		//
		//p := make([]byte, msg.Len())
		//_, err := msg.Encode(p)
		//if err != nil {
		//	Log.Errorc(func() string {
		//		return fmt.Sprintf("msg.Encode(p)")
		//	})
		//	return total, io.EOF
		//}
		// There's some data, let's process it first
		if len(*p) > 0 {
			n, err := w.Write(*p)
			total += int64(n)
			Log.Debugc(func() string {
				return fmt.Sprintf("Wrote %d bytes, totaling %d bytes", n, total)
			})

			if err != nil {
				Log.Errorc(func() string {
					return fmt.Sprintf("w.Write(p) error")
				})
				return total, err
			}
		}

		return total, nil
	}
}

/**
2016.03.03 修改
*/
func (this *buffer) isDone() bool {
	if atomic.LoadInt64(&this.done) == 1 {
		return true
	}

	return false
}

func powerOfTwo64(n int64) bool {
	return n != 0 && (n & (n - 1)) == 0
}

func roundUpPowerOfTwo64(n int64) int64 {
	n--
	n |= n >> 1
	n |= n >> 2
	n |= n >> 4
	n |= n >> 8
	n |= n >> 16
	n |= n >> 32
	n++

	return n
}
<|endoftext|>


<commit_before>package toolbox

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"reflect"
	"strings"
	"time"
)

var jsonContentType = "application/json"
var textPlainContentType = "text/plain"

const (
	MethodGet     = "GET"
	MethodHead    = "HEAD"
	MethodPost    = "POST"
	MethodPut     = "PUT"
	MethodPatch   = "PATCH" // RFC 5789
	MethodDelete  = "DELETE"
	MethodOptions = "OPTIONS"
	MethodTrace   = "TRACE"
)

var httpMethods = map[string]bool{
	MethodDelete:  true,
	MethodGet:     true,
	MethodPatch:   true,
	MethodPost:    true,
	MethodPut:     true,
	MethodHead:    true,
	MethodTrace:   true,
	MethodOptions: true,
}

//HandlerInvoker method is responsible  of passing required parameters to router handler.
type HandlerInvoker func(serviceRouting *ServiceRouting, request *http.Request, response http.ResponseWriter, parameters map[string]interface{}) error

//DefaultEncoderFactory  - NewJSONEncoderFactory
var DefaultEncoderFactory = NewJSONEncoderFactory()

//DefaultDecoderFactory - NewJSONDecoderFactory
var DefaultDecoderFactory = NewJSONDecoderFactory()

//ServiceRouting represents a simple web services routing rule, which is matched with http request
type ServiceRouting struct {
	URI                 string      //matching uri
	Handler             interface{} //has to be func
	HTTPMethod          string
	Parameters          []string
	ContentTypeEncoders map[string]EncoderFactory //content type encoder factory
	ContentTypeDecoders map[string]DecoderFactory //content type decoder factory
	HandlerInvoker      HandlerInvoker            //optional function that will be used instead of reflection to invoke a handler.
}

func (sr ServiceRouting) getDecoderFactory(contentType string) DecoderFactory {
	if sr.ContentTypeDecoders != nil {
		if factory, found := sr.ContentTypeDecoders[contentType]; found {
			return factory
		}
	}
	return DefaultDecoderFactory
}

func (sr ServiceRouting) getEncoderFactory(contentType string) EncoderFactory {
	if sr.ContentTypeDecoders != nil {
		if factory, found := sr.ContentTypeEncoders[contentType]; found {
			return factory
		}
	}
	return DefaultEncoderFactory
}

func (sr ServiceRouting) extractParameterFromBody(parameterName string, targetType reflect.Type, request *http.Request) (interface{}, error) {
	targetValuePointer := reflect.New(targetType)
	contentType := getContentTypeOrJSONContentType(request.Header.Get("Content-Type"))
	decoderFactory := sr.getDecoderFactory(contentType)
	decoder := decoderFactory.Create(request.Body)
	if !strings.Contains(parameterName, ":") {
		err := decoder.Decode(targetValuePointer.Interface())
		if err != nil {
			return nil, fmt.Errorf("unable to extract %Tv due to %v", targetValuePointer.Interface(), err)
		}
	} else {
		var valueMap = make(map[string]interface{})
		pair := strings.SplitN(parameterName, ":", 2)
		valueMap[pair[1]] = targetValuePointer.Interface()
		err := decoder.Decode(&valueMap)
		if err != nil {
			return nil, fmt.Errorf("unable to extract %T due to %v", targetValuePointer.Interface(), err)
		}
	}
	return targetValuePointer.Interface(), nil
}

func (sr ServiceRouting) extractParameters(request *http.Request, response http.ResponseWriter) (map[string]interface{}, error) {
	var result = make(map[string]interface{})
	request.ParseForm()
	functionSignature := GetFuncSignature(sr.Handler)
	uriParameters, _ := ExtractURIParameters(sr.URI, request.RequestURI)
	for _, name := range sr.Parameters {
		value, found := uriParameters[name]
		if found {
			if strings.Contains(value, ",") {
				result[name] = strings.Split(value, ",")
			} else {
				result[name] = value
			}
			continue
		}

		value = request.Form.Get(name)
		if len(value) > 0 {
			result[name] = value
		} else {
			continue
		}
	}
	if HasSliceAnyElements(sr.Parameters, "@httpRequest") {
		result["@httpRequest"] = request
	}
	if HasSliceAnyElements(sr.Parameters, "@httpResponseWriter") {
		result["@httpResponseWriter"] = response
	}

	if request.ContentLength > 0 {
		for i, parameter := range sr.Parameters {
			if _, found := result[parameter]; !found {
				value, err := sr.extractParameterFromBody(parameter, functionSignature[i], request)
				if err != nil {
					return nil, fmt.Errorf("failed to extract parameters for %v %v due to %v", sr.HTTPMethod, sr.URI, err)
				}
				result[parameter] = value
				break
			}
		}
	}
	return result, nil
}

//ServiceRouter represents routing rule
type ServiceRouter struct {
	serviceRouting []*ServiceRouting
}

func (r *ServiceRouter) match(request *http.Request) []*ServiceRouting {
	var result = make([]*ServiceRouting, 0)
	for _, candidate := range r.serviceRouting {
		if candidate.HTTPMethod == request.Method {
			_, matched := ExtractURIParameters(candidate.URI, request.RequestURI)
			if matched {
				result = append(result, candidate)
			}
		}
	}
	return result
}

func getContentTypeOrJSONContentType(contentType string) string {
	if strings.Contains(contentType, textPlainContentType) || strings.Contains(contentType, jsonContentType) || contentType == "" {
		return jsonContentType
	}
	return contentType
}

//Route matches  service routing by http method , and number of parameters, then it call routing method, and sent back its response.
func (r *ServiceRouter) Route(response http.ResponseWriter, request *http.Request) error {
	candidates := r.match(request)
	if len(candidates) == 0 {
		var uriTemplates = make([]string, 0)
		for _, routing := range r.serviceRouting {
			uriTemplates = append(uriTemplates, routing.URI)
		}
		return fmt.Errorf("failed to route request - unable to match %v with one of %v", request.RequestURI, strings.Join(uriTemplates, ","))
	}
	var finalError error

	for _, serviceRouting := range candidates {

		parameterValues, err := serviceRouting.extractParameters(request, response)
		if err != nil {
			finalError = fmt.Errorf("unable to extract parameters due to %v", err)
			continue
		}

		if serviceRouting.HandlerInvoker != nil {
			err := serviceRouting.HandlerInvoker(serviceRouting, request, response, parameterValues)
			if err != nil {
				finalError = fmt.Errorf("unable to extract parameters due to %v", err)
			}
			continue
		}

		functionParameters, err := BuildFunctionParameters(serviceRouting.Handler, serviceRouting.Parameters, parameterValues)
		if err != nil {
			finalError = fmt.Errorf("unable to build function parameters %T due to %v", serviceRouting.Handler, err)
			continue
		}

		result := CallFunction(serviceRouting.Handler, functionParameters...)
		if len(result) > 0 {
			err = WriteServiceRoutingResponse(response, request, serviceRouting, result[0])
			if err != nil {
				return fmt.Errorf("failed to write response response %v, due to %v", result[0], err)
			}
			return nil
		}
		response.Header().Set("Content-Type", textPlainContentType)
	}
	if finalError != nil {
		return fmt.Errorf("failed to route request - %v", finalError)
	}
	return nil
}

//WriteServiceRoutingResponse writes service router response
func WriteServiceRoutingResponse(response http.ResponseWriter, request *http.Request, serviceRouting *ServiceRouting, result interface{}) error {
	requestContentType := request.Header.Get("Content-Type")
	responseContentType := getContentTypeOrJSONContentType(requestContentType)
	encoderFactory := serviceRouting.getEncoderFactory(responseContentType)
	encoder := encoderFactory.Create(response)
	response.Header().Set("Content-Type", responseContentType)
	err := encoder.Encode(result)
	if err != nil {
		return fmt.Errorf("failed to encode response %v, due to %v", response, err)
	}
	return nil

	if err != nil {
		return fmt.Errorf("failed to write response response %v, due to %v", result, err)
	}
	return nil
}

//WriteResponse writes response to response writer, it used encoder factory to encode passed in response to the writer, it sets back request contenttype to response.
func (r *ServiceRouter) WriteResponse(encoderFactory EncoderFactory, response interface{}, request *http.Request, responseWriter http.ResponseWriter) error {
	requestContentType := request.Header.Get("Content-Type")
	responseContentType := getContentTypeOrJSONContentType(requestContentType)
	encoder := encoderFactory.Create(responseWriter)
	responseWriter.Header().Set("Content-Type", responseContentType)
	err := encoder.Encode(response)
	if err != nil {
		return fmt.Errorf("failed to encode response %v, due to %v", response, err)
	}
	return nil
}

//NewServiceRouter creates a new service router, is takes list of service routing as arguments
func NewServiceRouter(serviceRouting ...ServiceRouting) *ServiceRouter {
	var routings = make([]*ServiceRouting, 0)
	for i := range serviceRouting {
		routings = append(routings, &serviceRouting[i])
	}
	return &ServiceRouter{routings}
}

//RouteToService calls web service url, with passed in json request, and encodes http json response into passed response
func RouteToService(method, url string, request, response interface{}) (err error) {
	client, err := NewToolboxHTTPClient()
	if err != nil {
		return err
	}
	return client.Request(method, url, request, response, NewJSONEncoderFactory(), NewJSONDecoderFactory())
}

type HttpOptions struct {
	Key   string
	Value interface{}
}

func NewHttpClient(options ...*HttpOptions) (*http.Client, error) {
	if len(options) == 0 {
		return http.DefaultClient, nil
	}

	var (
		// Default values matching DefaultHttpClient
		RequestTimeoutMs      = 30 * time.Second
		KeepAliveTimeMs       = 30 * time.Second
		TLSHandshakeTimeoutMs = 10 * time.Second
		ExpectContinueTimeout = 1 * time.Second
		IdleConnTimeout       = 90 * time.Second
		DualStack             = true
		MaxIdleConnsPerHost   = http.DefaultMaxIdleConnsPerHost
		MaxIdleConns          = 100

		ResponseHeaderTimeoutMs time.Duration
		TimeoutMs               time.Duration
	)

	for _, option := range options {
		switch option.Key {
		case "RequestTimeoutMs":
			RequestTimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "TimeoutMs":
			TimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "KeepAliveTimeMs":
			KeepAliveTimeMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "TLSHandshakeTimeoutMs":
			KeepAliveTimeMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "ResponseHeaderTimeoutMs":
			ResponseHeaderTimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "MaxIdleConns":
			MaxIdleConns = AsInt(option.Value)
		default:
			return nil, fmt.Errorf("Invalid option: %v", option.Key)

		}
	}
	roundTripper := http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   RequestTimeoutMs,
			KeepAlive: KeepAliveTimeMs,
			DualStack: DualStack,
		}).Dial,
		MaxIdleConns:          MaxIdleConns,
		ExpectContinueTimeout: ExpectContinueTimeout,
		IdleConnTimeout:       IdleConnTimeout,
		TLSHandshakeTimeout:   TLSHandshakeTimeoutMs,
		MaxIdleConnsPerHost:   MaxIdleConnsPerHost,
		ResponseHeaderTimeout: ResponseHeaderTimeoutMs,
	}

	return &http.Client{
		Transport: &roundTripper,
		Timeout:   TimeoutMs,
	}, nil

}

// ToolboxHTTPClient contains preconfigured http client
type ToolboxHTTPClient struct {
	httpClient *http.Client
}

// NewToolboxHTTPClient instantiate new client with provided options
func NewToolboxHTTPClient(options ...*HttpOptions) (*ToolboxHTTPClient, error) {
	client, err := NewHttpClient(options...)
	if err != nil {
		return nil, err
	}
	return &ToolboxHTTPClient{client}, nil
}

// Request sends http request using the existing client
func (c *ToolboxHTTPClient) Request(method, url string, request, response interface{}, encoderFactory EncoderFactory, decoderFactory DecoderFactory) (err error) {
	if _, found := httpMethods[strings.ToUpper(method)]; !found {
		return errors.New("unsupported method:" + method)
	}
	var buffer *bytes.Buffer
	if request != nil {
		buffer = new(bytes.Buffer)
		err := encoderFactory.Create(buffer).Encode(&request)
		if err != nil {
			return fmt.Errorf("failed to encode request: %v due to ", err)
		}
	}
	var serverResponse *http.Response
	var httpRequest *http.Request
	httpMethod := strings.ToUpper(method)
	if request != nil {
		httpRequest, err = http.NewRequest(httpMethod, url, buffer)
		if err != nil {
			return err
		}
		httpRequest.Header.Set("Content-Type", jsonContentType)
	} else {
		httpRequest, err = http.NewRequest(httpMethod, url, nil)
		if err != nil {
			return err
		}
	}

	serverResponse, err = c.httpClient.Do(httpRequest)
	if serverResponse != nil {
		// must close we have serverResponse to avoid fd leak
		defer serverResponse.Body.Close()
	}
	if err != nil && serverResponse != nil {
		return fmt.Errorf("failed to get response %v %v", err, serverResponse.Header.Get("error"))
	}

	if response != nil {
		if serverResponse == nil || serverResponse.Body == nil {
			return fmt.Errorf("failed to receive response %v", err)
		}
		body, err := ioutil.ReadAll(serverResponse.Body)
		if err != nil {
			return fmt.Errorf("failed to read response %v", err)
		}
		err = decoderFactory.Create(strings.NewReader(string(body))).Decode(response)
		if err != nil {
			return fmt.Errorf("failed to decode response to %T: body: %v: %v, %v", response, string(body), err, serverResponse.Header.Get("error"))
		}
	}
	return nil
}
<commit_msg>updated service rotuer<commit_after>package toolbox

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"reflect"
	"strings"
	"time"
)

var jsonContentType = "application/json"
var textPlainContentType = "text/plain"

const (
	MethodGet     = "GET"
	MethodHead    = "HEAD"
	MethodPost    = "POST"
	MethodPut     = "PUT"
	MethodPatch   = "PATCH" // RFC 5789
	MethodDelete  = "DELETE"
	MethodOptions = "OPTIONS"
	MethodTrace   = "TRACE"
)

var httpMethods = map[string]bool{
	MethodDelete:  true,
	MethodGet:     true,
	MethodPatch:   true,
	MethodPost:    true,
	MethodPut:     true,
	MethodHead:    true,
	MethodTrace:   true,
	MethodOptions: true,
}

//HandlerInvoker method is responsible  of passing required parameters to router handler.
type HandlerInvoker func(serviceRouting *ServiceRouting, request *http.Request, response http.ResponseWriter, parameters map[string]interface{}) error

//DefaultEncoderFactory  - NewJSONEncoderFactory
var DefaultEncoderFactory = NewJSONEncoderFactory()

//DefaultDecoderFactory - NewJSONDecoderFactory
var DefaultDecoderFactory = NewJSONDecoderFactory()

//ServiceRouting represents a simple web services routing rule, which is matched with http request
type ServiceRouting struct {
	URI                 string      //matching uri
	Handler             interface{} //has to be func
	HTTPMethod          string
	Parameters          []string
	ContentTypeEncoders map[string]EncoderFactory //content type encoder factory
	ContentTypeDecoders map[string]DecoderFactory //content type decoder factory
	HandlerInvoker      HandlerInvoker            //optional function that will be used instead of reflection to invoke a handler.
}

func (sr ServiceRouting) getDecoderFactory(contentType string) DecoderFactory {
	if sr.ContentTypeDecoders != nil {
		if factory, found := sr.ContentTypeDecoders[contentType]; found {
			return factory
		}
	}
	return DefaultDecoderFactory
}

func (sr ServiceRouting) getEncoderFactory(contentType string) EncoderFactory {
	if sr.ContentTypeDecoders != nil {
		if factory, found := sr.ContentTypeEncoders[contentType]; found {
			return factory
		}
	}
	return DefaultEncoderFactory
}

func (sr ServiceRouting) extractParameterFromBody(parameterName string, targetType reflect.Type, request *http.Request) (interface{}, error) {
	targetValuePointer := reflect.New(targetType)
	contentType := getContentTypeOrJSONContentType(request.Header.Get("Content-Type"))
	decoderFactory := sr.getDecoderFactory(contentType)
	decoder := decoderFactory.Create(request.Body)
	if !strings.Contains(parameterName, ":") {
		err := decoder.Decode(targetValuePointer.Interface())
		if err != nil {
			return nil, fmt.Errorf("unable to extract %Tv due to %v", targetValuePointer.Interface(), err)
		}
	} else {
		var valueMap = make(map[string]interface{})
		pair := strings.SplitN(parameterName, ":", 2)
		valueMap[pair[1]] = targetValuePointer.Interface()
		err := decoder.Decode(&valueMap)
		if err != nil {
			return nil, fmt.Errorf("unable to extract %T due to %v", targetValuePointer.Interface(), err)
		}
	}
	return targetValuePointer.Interface(), nil
}

func (sr ServiceRouting) extractParameters(request *http.Request, response http.ResponseWriter) (map[string]interface{}, error) {
	var result = make(map[string]interface{})
	request.ParseForm()
	functionSignature := GetFuncSignature(sr.Handler)
	uriParameters, _ := ExtractURIParameters(sr.URI, request.RequestURI)
	for _, name := range sr.Parameters {
		value, found := uriParameters[name]
		if found {
			if strings.Contains(value, ",") {
				result[name] = strings.Split(value, ",")
			} else {
				result[name] = value
			}
			continue
		}

		value = request.Form.Get(name)
		if len(value) > 0 {
			result[name] = value
		} else {
			continue
		}
	}
	if HasSliceAnyElements(sr.Parameters, "@httpRequest") {
		result["@httpRequest"] = request
	}
	if HasSliceAnyElements(sr.Parameters, "@httpResponseWriter") {
		result["@httpResponseWriter"] = response
	}

	if request.ContentLength > 0 {
		for i, parameter := range sr.Parameters {
			if _, found := result[parameter]; !found {
				value, err := sr.extractParameterFromBody(parameter, functionSignature[i], request)
				if err != nil {
					return nil, fmt.Errorf("failed to extract parameters for %v %v due to %v", sr.HTTPMethod, sr.URI, err)
				}
				result[parameter] = value
				break
			}
		}
	}
	return result, nil
}

//ServiceRouter represents routing rule
type ServiceRouter struct {
	serviceRouting []*ServiceRouting
}

func (r *ServiceRouter) match(request *http.Request) []*ServiceRouting {
	var result = make([]*ServiceRouting, 0)
	for _, candidate := range r.serviceRouting {
		if candidate.HTTPMethod == request.Method {
			_, matched := ExtractURIParameters(candidate.URI, request.RequestURI)
			if matched {
				result = append(result, candidate)
			}
		}
	}
	return result
}

func getContentTypeOrJSONContentType(contentType string) string {
	if strings.Contains(contentType, textPlainContentType) || strings.Contains(contentType, jsonContentType) || contentType == "" {
		return jsonContentType
	}
	return contentType
}

//Route matches  service routing by http method , and number of parameters, then it call routing method, and sent back its response.
func (r *ServiceRouter) Route(response http.ResponseWriter, request *http.Request) error {
	candidates := r.match(request)
	if len(candidates) == 0 {
		var uriTemplates = make([]string, 0)
		for _, routing := range r.serviceRouting {
			uriTemplates = append(uriTemplates, routing.URI)
		}
		return fmt.Errorf("failed to route request - unable to match %v with one of %v", request.RequestURI, strings.Join(uriTemplates, ","))
	}
	var finalError error

	for _, serviceRouting := range candidates {

		parameterValues, err := serviceRouting.extractParameters(request, response)
		if err != nil {
			finalError = fmt.Errorf("unable to extract parameters due to %v", err)
			continue
		}

		if serviceRouting.HandlerInvoker != nil {
			err := serviceRouting.HandlerInvoker(serviceRouting, request, response, parameterValues)
			if err != nil {
				finalError = fmt.Errorf("unable to extract parameters due to %v", err)
			}
			continue
		}

		functionParameters, err := BuildFunctionParameters(serviceRouting.Handler, serviceRouting.Parameters, parameterValues)
		if err != nil {
			finalError = fmt.Errorf("unable to build function parameters %T due to %v", serviceRouting.Handler, err)
			continue
		}

		result := CallFunction(serviceRouting.Handler, functionParameters...)
		if len(result) > 0 {
			err = WriteServiceRoutingResponse(response, request, serviceRouting, result[0])
			if err != nil {
				return fmt.Errorf("failed to write response response %v, due to %v", result[0], err)
			}
			return nil
		}
		response.Header().Set("Content-Type", textPlainContentType)
	}
	if finalError != nil {
		return fmt.Errorf("failed to route request - %v", finalError)
	}
	return nil
}

//WriteServiceRoutingResponse writes service router response
func WriteServiceRoutingResponse(response http.ResponseWriter, request *http.Request, serviceRouting *ServiceRouting, result interface{}) error {
	requestContentType := request.Header.Get("Content-Type")
	responseContentType := getContentTypeOrJSONContentType(requestContentType)
	encoderFactory := serviceRouting.getEncoderFactory(responseContentType)
	encoder := encoderFactory.Create(response)
	response.Header().Set("Content-Type", responseContentType)
	err := encoder.Encode(result)
	if err != nil {
		return fmt.Errorf("failed to encode response %v, due to %v", response, err)
	}
	return nil

	if err != nil {
		return fmt.Errorf("failed to write response response %v, due to %v", result, err)
	}
	return nil
}

//WriteResponse writes response to response writer, it used encoder factory to encode passed in response to the writer, it sets back request contenttype to response.
func (r *ServiceRouter) WriteResponse(encoderFactory EncoderFactory, response interface{}, request *http.Request, responseWriter http.ResponseWriter) error {
	requestContentType := request.Header.Get("Content-Type")
	responseContentType := getContentTypeOrJSONContentType(requestContentType)
	encoder := encoderFactory.Create(responseWriter)
	responseWriter.Header().Set("Content-Type", responseContentType)
	err := encoder.Encode(response)
	if err != nil {
		return fmt.Errorf("failed to encode response %v, due to %v", response, err)
	}
	return nil
}

//NewServiceRouter creates a new service router, is takes list of service routing as arguments
func NewServiceRouter(serviceRouting ...ServiceRouting) *ServiceRouter {
	var routings = make([]*ServiceRouting, 0)
	for i := range serviceRouting {
		routings = append(routings, &serviceRouting[i])
	}
	return &ServiceRouter{routings}
}

//RouteToService calls web service url, with passed in json request, and encodes http json response into passed response
func RouteToService(method, url string, request, response interface{}) (err error) {
	client, err := NewToolboxHTTPClient()
	if err != nil {
		return err
	}
	return client.Request(method, url, request, response, NewJSONEncoderFactory(), NewJSONDecoderFactory())
}

type HttpOptions struct {
	Key   string
	Value interface{}
}

func NewHttpClient(options ...*HttpOptions) (*http.Client, error) {
	if len(options) == 0 {
		return http.DefaultClient, nil
	}

	var (
		// Default values matching DefaultHttpClient
		RequestTimeoutMs      = 30 * time.Second
		KeepAliveTimeMs       = 30 * time.Second
		TLSHandshakeTimeoutMs = 10 * time.Second
		ExpectContinueTimeout = 1 * time.Second
		IdleConnTimeout       = 90 * time.Second
		DualStack             = true
		MaxIdleConnsPerHost   = http.DefaultMaxIdleConnsPerHost
		MaxIdleConns          = 100

		ResponseHeaderTimeoutMs time.Duration
		TimeoutMs               time.Duration
	)

	for _, option := range options {
		switch option.Key {
		case "RequestTimeoutMs":
			RequestTimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "TimeoutMs":
			TimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "KeepAliveTimeMs":
			KeepAliveTimeMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "TLSHandshakeTimeoutMs":
			KeepAliveTimeMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "ResponseHeaderTimeoutMs":
			ResponseHeaderTimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond
		case "MaxIdleConns":
			MaxIdleConns = AsInt(option.Value)
		default:
			return nil, fmt.Errorf("Invalid option: %v", option.Key)

		}
	}
	roundTripper := http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   RequestTimeoutMs,
			KeepAlive: KeepAliveTimeMs,
			DualStack: DualStack,
		}).Dial,
		MaxIdleConns:          MaxIdleConns,
		ExpectContinueTimeout: ExpectContinueTimeout,
		IdleConnTimeout:       IdleConnTimeout,
		TLSHandshakeTimeout:   TLSHandshakeTimeoutMs,
		MaxIdleConnsPerHost:   MaxIdleConnsPerHost,
		ResponseHeaderTimeout: ResponseHeaderTimeoutMs,
	}

	return &http.Client{
		Transport: &roundTripper,
		Timeout:   TimeoutMs,
	}, nil

}

// ToolboxHTTPClient contains preconfigured http client
type ToolboxHTTPClient struct {
	httpClient *http.Client
}

// NewToolboxHTTPClient instantiate new client with provided options
func NewToolboxHTTPClient(options ...*HttpOptions) (*ToolboxHTTPClient, error) {
	client, err := NewHttpClient(options...)
	if err != nil {
		return nil, err
	}
	return &ToolboxHTTPClient{client}, nil
}

// Request sends http request using the existing client
func (c *ToolboxHTTPClient) Request(method, url string, request, response interface{}, encoderFactory EncoderFactory, decoderFactory DecoderFactory) (err error) {
	if _, found := httpMethods[strings.ToUpper(method)]; !found {
		return errors.New("unsupported method:" + method)
	}
	var buffer *bytes.Buffer

	if request != nil {
		buffer = new(bytes.Buffer)
		if IsString(request) {
			buffer.Write([]byte(AsString(request)))
		} else {
			err := encoderFactory.Create(buffer).Encode(&request)
			if err != nil {
				return fmt.Errorf("failed to encode request: %v due to ", err)
			}
		}
	}
	var serverResponse *http.Response
	var httpRequest *http.Request
	httpMethod := strings.ToUpper(method)
	if request != nil {
		httpRequest, err = http.NewRequest(httpMethod, url, buffer)
		if err != nil {
			return err
		}
		httpRequest.Header.Set("Content-Type", jsonContentType)
	} else {
		httpRequest, err = http.NewRequest(httpMethod, url, nil)
		if err != nil {
			return err
		}
	}

	serverResponse, err = c.httpClient.Do(httpRequest)
	if serverResponse != nil {
		// must close we have serverResponse to avoid fd leak
		defer serverResponse.Body.Close()
	}
	if err != nil && serverResponse != nil {
		return fmt.Errorf("failed to get response %v %v", err, serverResponse.Header.Get("error"))
	}

	if response != nil {
		statusSettable, canSetStatus := response.(StatusCodeSettable)
		if canSetStatus {
			statusSettable.SetStatusCode(serverResponse.StatusCode)
		}
		if serverResponse == nil  {
			return fmt.Errorf("failed to receive response %v", err)
		}
		var errorPrefix = fmt.Sprintf("failed to process response: %v, ", serverResponse.StatusCode)
		body, err := ioutil.ReadAll(serverResponse.Body)
		if err != nil {
			return fmt.Errorf("%v unable read body %v",errorPrefix, err)
		}
		if len(body) == 0 {
			return fmt.Errorf("%v response body was empty", errorPrefix)
		}

		err = decoderFactory.Create(strings.NewReader(string(body))).Decode(response)
		if err != nil {
			return fmt.Errorf("%v. unable decode response as %T: body: %v: %v", errorPrefix, response, string(body), err)
		}
		if canSetStatus {
			statusSettable.SetStatusCode(serverResponse.StatusCode)
		}
	}
	return nil
}

type StatusCodeSettable interface {
	SetStatusCode(code int)
}<|endoftext|>


<commit_before>package client

import (
	"encoding/json"
	"net/url"
	"strconv"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/swarm"
	"golang.org/x/net/context"
)

// ServiceUpdate updates a Service.
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
	var (
		headers map[string][]string
		query   = url.Values{}
	)

	if options.EncodedRegistryAuth != "" {
		headers = map[string][]string{
			"X-Registry-Auth": {options.EncodedRegistryAuth},
		}
	}

	if options.RegistryAuthFrom != "" {
		query.Set("registryAuthFrom", options.RegistryAuthFrom)
	}

	query.Set("version", strconv.FormatUint(version.Index, 10))

	var response types.ServiceUpdateResponse
	resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
	if err != nil {
		return response, err
	}

	err = json.NewDecoder(resp.body).Decode(&response)
	ensureReaderClosed(resp)
	return response, err
}
<commit_msg>Implement server-side rollback, for daemon versions that support this<commit_after>package client

import (
	"encoding/json"
	"net/url"
	"strconv"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/swarm"
	"golang.org/x/net/context"
)

// ServiceUpdate updates a Service.
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
	var (
		headers map[string][]string
		query   = url.Values{}
	)

	if options.EncodedRegistryAuth != "" {
		headers = map[string][]string{
			"X-Registry-Auth": {options.EncodedRegistryAuth},
		}
	}

	if options.RegistryAuthFrom != "" {
		query.Set("registryAuthFrom", options.RegistryAuthFrom)
	}

	if options.Rollback != "" {
		query.Set("rollback", options.Rollback)
	}

	query.Set("version", strconv.FormatUint(version.Index, 10))

	var response types.ServiceUpdateResponse
	resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
	if err != nil {
		return response, err
	}

	err = json.NewDecoder(resp.body).Decode(&response)
	ensureReaderClosed(resp)
	return response, err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math"
	"path/filepath"

	"github.com/spf13/cobra"

	"github.com/NebulousLabs/Sia/api"
)

// filesize returns a string that displays a filesize in human-readable units.
func filesizeUnits(size int64) string {
	if size == 0 {
		return "0 B"
	}
	sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
	i := int(math.Log10(float64(size)) / 3)
	return fmt.Sprintf("%.*f %s", i, float64(size)/math.Pow10(3*i), sizes[i])
}

var (
	renterCmd = &cobra.Command{
		Use:   "renter",
		Short: "Perform renter actions",
		Long:  "Upload, download, rename, delete, load, or share files.",
		Run:   wrap(renterfileslistcmd),
	}

	renterDownloadQueueCmd = &cobra.Command{
		Use:   "queue",
		Short: "View the download queue",
		Long:  "View the list of files that have been downloaded.",
		Run:   wrap(renterdownloadqueuecmd),
	}

	renterFilesDeleteCmd = &cobra.Command{
		Use:   "delete [nickname]",
		Short: "Delete a file",
		Long:  "Delete a file. Does not delete the file on disk.",
		Run:   wrap(renterfilesdeletecmd),
	}

	renterFilesDownloadCmd = &cobra.Command{
		Use:   "download [nickname] [destination]",
		Short: "Download a file",
		Long:  "Download a previously-uploaded file to a specified destination.",
		Run:   wrap(renterfilesdownloadcmd),
	}

	renterFilesListCmd = &cobra.Command{
		Use:   "list",
		Short: "List the status of all files",
		Long:  "List the status of all files known to the renter.",
		Run:   wrap(renterfileslistcmd),
	}

	renterFilesLoadCmd = &cobra.Command{
		Use:   "load [filename]",
		Short: "Load a .sia file",
		Long:  "Load a .sia file, adding the file entries contained within.",
		Run:   wrap(renterfilesloadcmd),
	}

	renterFilesLoadASCIICmd = &cobra.Command{
		Use:   "loadascii [data]",
		Short: "Load an ASCII-encoded .sia file",
		Long:  "Load an ASCII-encoded .sia file.",
		Run:   wrap(renterfilesloadasciicmd),
	}

	renterFilesRenameCmd = &cobra.Command{
		Use:   "rename [nickname] [newname]",
		Short: "Rename a file",
		Long:  "Rename a file.",
		Run:   wrap(renterfilesrenamecmd),
	}

	renterFilesShareCmd = &cobra.Command{
		Use:   "share [nickname] [filepath]",
		Short: "Export a file to a .sia for sharing",
		Long:  "Export a file to a .sia for sharing.",
		Run:   wrap(renterfilessharecmd),
	}

	renterFilesShareASCIICmd = &cobra.Command{
		Use:   "shareascii [nickname]",
		Short: "Export a file as an ASCII-encoded .sia file",
		Long:  "Export a file as an ASCII-encoded .sia file.",
		Run:   wrap(renterfilesshareasciicmd),
	}

	renterFilesUploadCmd = &cobra.Command{
		Use:   "upload [filename] [nickname]",
		Short: "Upload a file",
		Long:  "Upload a file using a given nickname.",
		Run:   wrap(renterfilesuploadcmd),
	}
)

// abs returns the absolute representation of a path.
// TODO: bad things can happen if you run siac from a non-existent directory.
// Implement some checks to catch this problem.
func abs(path string) string {
	abspath, err := filepath.Abs(path)
	if err != nil {
		return path
	}
	return abspath
}

func renterdownloadqueuecmd() {
	var queue []api.DownloadInfo
	err := getAPI("/renter/downloadqueue", &queue)
	if err != nil {
		fmt.Println("Could not get download queue:", err)
		return
	}
	if len(queue) == 0 {
		fmt.Println("No downloads to show.")
		return
	}
	fmt.Println("Download Queue:")
	for _, file := range queue {
		fmt.Printf("%s: %5.1f%% %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), 100*float32(file.Received)/float32(file.Filesize), file.Nickname, file.Destination)
	}
}

func renterfilesdeletecmd(nickname string) {
	err := post("/renter/files/delete", "nickname="+nickname)
	if err != nil {
		fmt.Println("Could not delete file:", err)
		return
	}
	fmt.Println("Deleted", nickname)
}

func renterfilesdownloadcmd(nickname, destination string) {
	err := post("/renter/files/download", fmt.Sprintf("nickname=%s&destination=%s", nickname, abs(destination)))
	if err != nil {
		fmt.Println("Could not download file:", err)
		return
	}
	fmt.Printf("Downloaded '%s' to %s.\n", nickname, abs(destination))
}

func renterfileslistcmd() {
	var files []api.FileInfo
	err := getAPI("/renter/files/list", &files)
	if err != nil {
		fmt.Println("Could not get file list:", err)
		return
	}
	if len(files) == 0 {
		fmt.Println("No files have been uploaded.")
		return
	}
	fmt.Println("Tracking", len(files), "files:")
	for _, file := range files {
		// TODO: write a filesize() helper function to display proper units
		if file.Available {
			fmt.Printf("%13s  %s\n", filesizeUnits(int64(file.Filesize)), file.Nickname)
		} else {
			fmt.Printf("%13s  %s (uploading, %0.2f%%)\n", filesizeUnits(int64(file.Filesize)), file.Nickname, file.UploadProgress)
		}
	}
}

func renterfilesloadcmd(filename string) {
	info := new(api.RenterFilesLoadResponse)
	err := postResp("/renter/files/load", "filename="+abs(filename), info)
	if err != nil {
		fmt.Println("Could not load file:", err)
		return
	}
	fmt.Printf("Loaded %d files:\n", len(info.FilesAdded))
	for _, file := range info.FilesAdded {
		fmt.Printf("\t%s\n", file)
	}
}

func renterfilesloadasciicmd(data string) {
	info := new(api.RenterFilesLoadResponse)
	err := getAPI(fmt.Sprintf("/renter/files/loadascii?file=%s", data), info)
	if err != nil {
		fmt.Println("Could not load file:", err)
		return
	}
	fmt.Printf("Loaded %d files:\n", len(info.FilesAdded))
	for _, file := range info.FilesAdded {
		fmt.Printf("\t%s\n", file)
	}
}

func renterfilesrenamecmd(nickname, newname string) {
	err := post("/renter/files/rename", fmt.Sprintf("nickname=%s&newname=%s", nickname, newname))
	if err != nil {
		fmt.Println("Could not rename file:", err)
		return
	}
	fmt.Printf("Renamed %s to %s\n", nickname, newname)
}

func renterfilessharecmd(nickname, destination string) {
	err := get(fmt.Sprintf("/renter/files/share?nickname=%s&filepath=%s", nickname, abs(destination)))
	if err != nil {
		fmt.Println("Could not share file:", err)
		return
	}
	fmt.Printf("Exported %s to %s\n", nickname, abs(destination))
}

func renterfilesshareasciicmd(nickname string) {
	var data struct{ File string }
	err := getAPI(fmt.Sprintf("/renter/files/shareascii?nickname=%s", nickname), &data)
	if err != nil {
		fmt.Println("Could not share file:", err)
		return
	}
	fmt.Println(data.File)
}

func renterfilesuploadcmd(source, nickname string) {
	err := post("/renter/files/upload", fmt.Sprintf("source=%s&nickname=%s", abs(source), nickname))
	if err != nil {
		fmt.Println("Could not upload file:", err)
		return
	}
	fmt.Printf("Uploaded '%s' as %s.\n", abs(source), nickname)
}
<commit_msg>files -> file(s)<commit_after>package main

import (
	"fmt"
	"math"
	"path/filepath"

	"github.com/spf13/cobra"

	"github.com/NebulousLabs/Sia/api"
)

// filesize returns a string that displays a filesize in human-readable units.
func filesizeUnits(size int64) string {
	if size == 0 {
		return "0 B"
	}
	sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
	i := int(math.Log10(float64(size)) / 3)
	return fmt.Sprintf("%.*f %s", i, float64(size)/math.Pow10(3*i), sizes[i])
}

var (
	renterCmd = &cobra.Command{
		Use:   "renter",
		Short: "Perform renter actions",
		Long:  "Upload, download, rename, delete, load, or share files.",
		Run:   wrap(renterfileslistcmd),
	}

	renterDownloadQueueCmd = &cobra.Command{
		Use:   "queue",
		Short: "View the download queue",
		Long:  "View the list of files that have been downloaded.",
		Run:   wrap(renterdownloadqueuecmd),
	}

	renterFilesDeleteCmd = &cobra.Command{
		Use:   "delete [nickname]",
		Short: "Delete a file",
		Long:  "Delete a file. Does not delete the file on disk.",
		Run:   wrap(renterfilesdeletecmd),
	}

	renterFilesDownloadCmd = &cobra.Command{
		Use:   "download [nickname] [destination]",
		Short: "Download a file",
		Long:  "Download a previously-uploaded file to a specified destination.",
		Run:   wrap(renterfilesdownloadcmd),
	}

	renterFilesListCmd = &cobra.Command{
		Use:   "list",
		Short: "List the status of all files",
		Long:  "List the status of all files known to the renter.",
		Run:   wrap(renterfileslistcmd),
	}

	renterFilesLoadCmd = &cobra.Command{
		Use:   "load [filename]",
		Short: "Load a .sia file",
		Long:  "Load a .sia file, adding the file entries contained within.",
		Run:   wrap(renterfilesloadcmd),
	}

	renterFilesLoadASCIICmd = &cobra.Command{
		Use:   "loadascii [data]",
		Short: "Load an ASCII-encoded .sia file",
		Long:  "Load an ASCII-encoded .sia file.",
		Run:   wrap(renterfilesloadasciicmd),
	}

	renterFilesRenameCmd = &cobra.Command{
		Use:   "rename [nickname] [newname]",
		Short: "Rename a file",
		Long:  "Rename a file.",
		Run:   wrap(renterfilesrenamecmd),
	}

	renterFilesShareCmd = &cobra.Command{
		Use:   "share [nickname] [filepath]",
		Short: "Export a file to a .sia for sharing",
		Long:  "Export a file to a .sia for sharing.",
		Run:   wrap(renterfilessharecmd),
	}

	renterFilesShareASCIICmd = &cobra.Command{
		Use:   "shareascii [nickname]",
		Short: "Export a file as an ASCII-encoded .sia file",
		Long:  "Export a file as an ASCII-encoded .sia file.",
		Run:   wrap(renterfilesshareasciicmd),
	}

	renterFilesUploadCmd = &cobra.Command{
		Use:   "upload [filename] [nickname]",
		Short: "Upload a file",
		Long:  "Upload a file using a given nickname.",
		Run:   wrap(renterfilesuploadcmd),
	}
)

// abs returns the absolute representation of a path.
// TODO: bad things can happen if you run siac from a non-existent directory.
// Implement some checks to catch this problem.
func abs(path string) string {
	abspath, err := filepath.Abs(path)
	if err != nil {
		return path
	}
	return abspath
}

func renterdownloadqueuecmd() {
	var queue []api.DownloadInfo
	err := getAPI("/renter/downloadqueue", &queue)
	if err != nil {
		fmt.Println("Could not get download queue:", err)
		return
	}
	if len(queue) == 0 {
		fmt.Println("No downloads to show.")
		return
	}
	fmt.Println("Download Queue:")
	for _, file := range queue {
		fmt.Printf("%s: %5.1f%% %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), 100*float32(file.Received)/float32(file.Filesize), file.Nickname, file.Destination)
	}
}

func renterfilesdeletecmd(nickname string) {
	err := post("/renter/files/delete", "nickname="+nickname)
	if err != nil {
		fmt.Println("Could not delete file:", err)
		return
	}
	fmt.Println("Deleted", nickname)
}

func renterfilesdownloadcmd(nickname, destination string) {
	err := post("/renter/files/download", fmt.Sprintf("nickname=%s&destination=%s", nickname, abs(destination)))
	if err != nil {
		fmt.Println("Could not download file:", err)
		return
	}
	fmt.Printf("Downloaded '%s' to %s.\n", nickname, abs(destination))
}

func renterfileslistcmd() {
	var files []api.FileInfo
	err := getAPI("/renter/files/list", &files)
	if err != nil {
		fmt.Println("Could not get file list:", err)
		return
	}
	if len(files) == 0 {
		fmt.Println("No files have been uploaded.")
		return
	}
	fmt.Println("Tracking", len(files), "files:")
	for _, file := range files {
		// TODO: write a filesize() helper function to display proper units
		if file.Available {
			fmt.Printf("%13s  %s\n", filesizeUnits(int64(file.Filesize)), file.Nickname)
		} else {
			fmt.Printf("%13s  %s (uploading, %0.2f%%)\n", filesizeUnits(int64(file.Filesize)), file.Nickname, file.UploadProgress)
		}
	}
}

func renterfilesloadcmd(filename string) {
	info := new(api.RenterFilesLoadResponse)
	err := postResp("/renter/files/load", "filename="+abs(filename), info)
	if err != nil {
		fmt.Println("Could not load file:", err)
		return
	}
	fmt.Printf("Loaded %d file(s):\n", len(info.FilesAdded))
	for _, file := range info.FilesAdded {
		fmt.Printf("\t%s\n", file)
	}
}

func renterfilesloadasciicmd(data string) {
	info := new(api.RenterFilesLoadResponse)
	err := getAPI(fmt.Sprintf("/renter/files/loadascii?file=%s", data), info)
	if err != nil {
		fmt.Println("Could not load file:", err)
		return
	}
	fmt.Printf("Loaded %d file(s):\n", len(info.FilesAdded))
	for _, file := range info.FilesAdded {
		fmt.Printf("\t%s\n", file)
	}
}

func renterfilesrenamecmd(nickname, newname string) {
	err := post("/renter/files/rename", fmt.Sprintf("nickname=%s&newname=%s", nickname, newname))
	if err != nil {
		fmt.Println("Could not rename file:", err)
		return
	}
	fmt.Printf("Renamed %s to %s\n", nickname, newname)
}

func renterfilessharecmd(nickname, destination string) {
	err := get(fmt.Sprintf("/renter/files/share?nickname=%s&filepath=%s", nickname, abs(destination)))
	if err != nil {
		fmt.Println("Could not share file:", err)
		return
	}
	fmt.Printf("Exported %s to %s\n", nickname, abs(destination))
}

func renterfilesshareasciicmd(nickname string) {
	var data struct{ File string }
	err := getAPI(fmt.Sprintf("/renter/files/shareascii?nickname=%s", nickname), &data)
	if err != nil {
		fmt.Println("Could not share file:", err)
		return
	}
	fmt.Println(data.File)
}

func renterfilesuploadcmd(source, nickname string) {
	err := post("/renter/files/upload", fmt.Sprintf("source=%s&nickname=%s", abs(source), nickname))
	if err != nil {
		fmt.Println("Could not upload file:", err)
		return
	}
	fmt.Printf("Uploaded '%s' as %s.\n", abs(source), nickname)
}
<|endoftext|>


<commit_before>package notify

import (
	"fmt"
	"sync"

	"github.com/abrander/gansoi/checks"
	"github.com/abrander/gansoi/database"
	"github.com/abrander/gansoi/eval"
	"github.com/abrander/gansoi/logger"
)

type (
	// Notifier takes care of notifying all contacts in a relevant ContactGroup.
	Notifier struct {
		db *database.Database
	}
)

var (
	cacheLock sync.RWMutex

	contactsCache = make(map[string]*Contact)
	groupsCache   = make(map[string]*ContactGroup)

	stateCacheLock sync.RWMutex
	stateCache     = make(map[string]eval.State)
)

// NewNotifier will start a new notifier service.
func NewNotifier(db *database.Database) (*Notifier, error) {
	n := &Notifier{
		db: db,
	}

	var contacts []Contact
	var groups []ContactGroup

	err := n.db.All(&contacts, -1, 0, false)
	if err != database.ErrNotFound && err != nil {
		return nil, err
	}

	err = n.db.All(&groups, -1, 0, false)
	if err != database.ErrNotFound && err != nil {
		return nil, err
	}

	cacheLock.Lock()
	for _, contact := range contacts {
		contactsCache[contact.ID] = &contact
	}

	for _, group := range groups {
		groupsCache[group.ID] = &group
	}
	cacheLock.Unlock()

	return n, nil
}

// PostClusterApply implements node.Listener.
func (n *Notifier) PostClusterApply(leader bool, command database.Command, data interface{}, err error) {
	if !leader {
		return
	}

	switch data.(type) {
	case *Contact:
		c := data.(*Contact)

		cacheLock.Lock()
		if command == database.CommandSave {
			contactsCache[c.ID] = c
		} else if command == database.CommandDelete {
			delete(contactsCache, c.ID)
		}
		cacheLock.Unlock()

	case *ContactGroup:
		g := data.(*ContactGroup)

		cacheLock.Lock()
		if command == database.CommandSave {
			groupsCache[g.ID] = g
		} else if command == database.CommandDelete {
			delete(groupsCache, g.ID)
		}
		cacheLock.Unlock()

	case *eval.Evaluation:
		e := n.gotEvaluation(data.(*eval.Evaluation))
		if e != nil {
			logger.Red("notify", "%s", e.Error())
		}
	}
}

func (n *Notifier) gotEvaluation(e *eval.Evaluation) error {
	var check checks.Check
	err := n.db.One("ID", e.CheckID, &check)
	if err != nil {
		return err
	}

	state := e.History.Last(3).Reduce()

	if state == eval.StateUnknown {
		logger.Green("notify", "Ignoring %s %s", state.String(), e.History.ColorString())
		return nil
	}

	duration := e.End.Sub(e.Start)

	stateCacheLock.RLock()
	lastState := stateCache[e.CheckID]
	stateCacheLock.RUnlock()

	if state == lastState {
		logger.Green("notify", "Ignoring unchanged state (Last: %s, Current: %s, Duration: %s) %s", lastState, state, duration.String(), e.History.ColorString())
		// Nothing changed. Abort.
		return nil
	}

	stateCacheLock.Lock()
	stateCache[e.CheckID] = state
	stateCacheLock.Unlock()

	if lastState == eval.StateUnknown {
		logger.Green("notify", "Ignoring %s when previous state is %s %v", state, lastState, e.History.ColorString())
		// Last state was unknown. Maybe we just started. Don't notify.
		return nil
	}

	if duration < check.Interval*2 && state == eval.StateDegraded {
		logger.Green("notify", "Ignoring %s for less than two cycles when degraded %s", state, e.History.ColorString())
		return nil
	}

	logger.Yellow("notify", "%s is %s %s", e.CheckID, state, e.History.ColorString())

	if len(check.ContactGroups) == 0 {
		logger.Green("notify", "Check '%s' has no-one to nofity, aborting", e.CheckID)
		return nil
	}

	text := fmt.Sprintf("%s is %s", e.CheckID, state.String())

	targetGroups := check.ContactGroups
	for _, groupID := range targetGroups {
		cacheLock.RLock()
		group, found := groupsCache[groupID]
		cacheLock.RUnlock()

		if !found {
			logger.Yellow("notify", "ContactGroup not found (%s)", groupID)
			continue
		}

		// FIXME: Handle errors somehow.
		logger.Green("notify", "Notifying '%s'", groupID)
		go group.Notify(text)
	}

	return nil
}
<commit_msg>Print the CheckID in log messages from notify.<commit_after>package notify

import (
	"fmt"
	"sync"

	"github.com/abrander/gansoi/checks"
	"github.com/abrander/gansoi/database"
	"github.com/abrander/gansoi/eval"
	"github.com/abrander/gansoi/logger"
)

type (
	// Notifier takes care of notifying all contacts in a relevant ContactGroup.
	Notifier struct {
		db *database.Database
	}
)

var (
	cacheLock sync.RWMutex

	contactsCache = make(map[string]*Contact)
	groupsCache   = make(map[string]*ContactGroup)

	stateCacheLock sync.RWMutex
	stateCache     = make(map[string]eval.State)
)

// NewNotifier will start a new notifier service.
func NewNotifier(db *database.Database) (*Notifier, error) {
	n := &Notifier{
		db: db,
	}

	var contacts []Contact
	var groups []ContactGroup

	err := n.db.All(&contacts, -1, 0, false)
	if err != database.ErrNotFound && err != nil {
		return nil, err
	}

	err = n.db.All(&groups, -1, 0, false)
	if err != database.ErrNotFound && err != nil {
		return nil, err
	}

	cacheLock.Lock()
	for _, contact := range contacts {
		contactsCache[contact.ID] = &contact
	}

	for _, group := range groups {
		groupsCache[group.ID] = &group
	}
	cacheLock.Unlock()

	return n, nil
}

// PostClusterApply implements node.Listener.
func (n *Notifier) PostClusterApply(leader bool, command database.Command, data interface{}, err error) {
	if !leader {
		return
	}

	switch data.(type) {
	case *Contact:
		c := data.(*Contact)

		cacheLock.Lock()
		if command == database.CommandSave {
			contactsCache[c.ID] = c
		} else if command == database.CommandDelete {
			delete(contactsCache, c.ID)
		}
		cacheLock.Unlock()

	case *ContactGroup:
		g := data.(*ContactGroup)

		cacheLock.Lock()
		if command == database.CommandSave {
			groupsCache[g.ID] = g
		} else if command == database.CommandDelete {
			delete(groupsCache, g.ID)
		}
		cacheLock.Unlock()

	case *eval.Evaluation:
		e := n.gotEvaluation(data.(*eval.Evaluation))
		if e != nil {
			logger.Red("notify", "%s", e.Error())
		}
	}
}

func (n *Notifier) gotEvaluation(e *eval.Evaluation) error {
	var check checks.Check
	err := n.db.One("ID", e.CheckID, &check)
	if err != nil {
		return err
	}

	state := e.History.Last(3).Reduce()

	if state == eval.StateUnknown {
		logger.Green("notify", "[%s] Ignoring %s %s", e.CheckID, state.String(), e.History.ColorString())
		return nil
	}

	duration := e.End.Sub(e.Start)

	stateCacheLock.RLock()
	lastState := stateCache[e.CheckID]
	stateCacheLock.RUnlock()

	if state == lastState {
		logger.Green("notify", "[%s] Ignoring unchanged state (Last: %s, Current: %s, Duration: %s) %s", e.CheckID, lastState, state, duration.String(), e.History.ColorString())
		// Nothing changed. Abort.
		return nil
	}

	stateCacheLock.Lock()
	stateCache[e.CheckID] = state
	stateCacheLock.Unlock()

	if lastState == eval.StateUnknown {
		logger.Green("notify", "[%s] Ignoring %s when previous state is %s %v", e.CheckID, state, lastState, e.History.ColorString())
		// Last state was unknown. Maybe we just started. Don't notify.
		return nil
	}

	if duration < check.Interval*2 && state == eval.StateDegraded {
		logger.Green("notify", "[%s] Ignoring %s for less than two cycles when degraded %s", e.CheckID, state, e.History.ColorString())
		return nil
	}

	logger.Yellow("notify", "%s is %s %s", e.CheckID, state, e.History.ColorString())

	if len(check.ContactGroups) == 0 {
		logger.Green("notify", "[%s] No-one to nofity, aborting", e.CheckID)
		return nil
	}

	text := fmt.Sprintf("%s is %s", e.CheckID, state.String())

	targetGroups := check.ContactGroups
	for _, groupID := range targetGroups {
		cacheLock.RLock()
		group, found := groupsCache[groupID]
		cacheLock.RUnlock()

		if !found {
			logger.Yellow("notify", "[%s] ContactGroup not found (%s)", e.CheckID, groupID)
			continue
		}

		// FIXME: Handle errors somehow.
		logger.Green("notify", "[%s] Notifying '%s'", e.CheckID, groupID)
		go group.Notify(text)
	}

	return nil
}
<|endoftext|>


<commit_before>package cli

import (
	"bytes"
	"io/ioutil"
	"testing"
)

func testApp() *App {
	app := NewApp()
	app.Name = "greet"
	app.Flags = []Flag{
		&StringFlag{
			Name:      "socket",
			Aliases:   []string{"s"},
			Usage:     "some 'usage' text",
			Value:     "value",
			TakesFile: true,
		},
		&StringFlag{Name: "flag", Aliases: []string{"fl", "f"}},
		&BoolFlag{
			Name:    "another-flag",
			Aliases: []string{"b"},
			Usage:   "another usage text",
		},
		&BoolFlag{
			Name:   "hidden-flag",
			Hidden: true,
		},
	}
	app.Commands = []*Command{{
		Aliases: []string{"c"},
		Flags: []Flag{
			&StringFlag{
				Name:      "flag",
				Aliases:   []string{"fl", "f"},
				TakesFile: true,
			},
			&BoolFlag{
				Name:    "another-flag",
				Aliases: []string{"b"},
				Usage:   "another usage text",
			},
		},
		Name:  "config",
		Usage: "another usage test",
		Subcommands: []*Command{{
			Aliases: []string{"s", "ss"},
			Flags: []Flag{
				&StringFlag{Name: "sub-flag", Aliases: []string{"sub-fl", "s"}},
				&BoolFlag{
					Name:    "sub-command-flag",
					Aliases: []string{"s"},
					Usage:   "some usage text",
				},
			},
			Name:  "sub-config",
			Usage: "another usage test",
		}},
	}, {
		Aliases: []string{"i", "in"},
		Name:    "info",
		Usage:   "retrieve generic information",
	}, {
		Name: "some-command",
	}, {
		Name:   "hidden-command",
		Hidden: true,
	}}
	app.UsageText = "app [first_arg] [second_arg]"
	app.Usage = "Some app"
	app.Authors = []*Author{
		{Name: "Harrison", Email: "harrison@lolwut.com"},
		{Name: "Oliver Allen", Email: "oliver@toyshop.com"},
	}
	return app
}

func expectFileContent(t *testing.T, file, expected string) {
	data, err := ioutil.ReadFile(file)
	// Ignore windows line endings
	// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
	expected = string(bytes.Replace([]byte(expected), []byte("\r\n"), []byte("\n"), -1))
	expect(t, err, nil)
	expect(t, string(data), expected)
}

func TestToMarkdownFull(t *testing.T) {
	// Given
	app := testApp()

	// When
	res, err := app.ToMarkdown()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-full.md", res)
}

func TestToMarkdownNoFlags(t *testing.T) {
	// Given
	app := testApp()
	app.Flags = nil

	// When
	res, err := app.ToMarkdown()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-no-flags.md", res)
}

func TestToMarkdownNoCommands(t *testing.T) {
	// Given
	app := testApp()
	app.Commands = nil

	// When
	res, err := app.ToMarkdown()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-no-commands.md", res)
}

func TestToMarkdownNoAuthors(t *testing.T) {
	// Given
	app := testApp()
	app.Authors = []*Author{}

	// When
	res, err := app.ToMarkdown()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-no-authors.md", res)
}

func TestToMan(t *testing.T) {
	// Given
	app := testApp()

	// When
	res, err := app.ToMan()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-full.man", res)
}
<commit_msg>fix github-actions for windows<commit_after>package cli

import (
	"bytes"
	"io/ioutil"
	"testing"
)

func testApp() *App {
	app := NewApp()
	app.Name = "greet"
	app.Flags = []Flag{
		&StringFlag{
			Name:      "socket",
			Aliases:   []string{"s"},
			Usage:     "some 'usage' text",
			Value:     "value",
			TakesFile: true,
		},
		&StringFlag{Name: "flag", Aliases: []string{"fl", "f"}},
		&BoolFlag{
			Name:    "another-flag",
			Aliases: []string{"b"},
			Usage:   "another usage text",
		},
		&BoolFlag{
			Name:   "hidden-flag",
			Hidden: true,
		},
	}
	app.Commands = []*Command{{
		Aliases: []string{"c"},
		Flags: []Flag{
			&StringFlag{
				Name:      "flag",
				Aliases:   []string{"fl", "f"},
				TakesFile: true,
			},
			&BoolFlag{
				Name:    "another-flag",
				Aliases: []string{"b"},
				Usage:   "another usage text",
			},
		},
		Name:  "config",
		Usage: "another usage test",
		Subcommands: []*Command{{
			Aliases: []string{"s", "ss"},
			Flags: []Flag{
				&StringFlag{Name: "sub-flag", Aliases: []string{"sub-fl", "s"}},
				&BoolFlag{
					Name:    "sub-command-flag",
					Aliases: []string{"s"},
					Usage:   "some usage text",
				},
			},
			Name:  "sub-config",
			Usage: "another usage test",
		}},
	}, {
		Aliases: []string{"i", "in"},
		Name:    "info",
		Usage:   "retrieve generic information",
	}, {
		Name: "some-command",
	}, {
		Name:   "hidden-command",
		Hidden: true,
	}}
	app.UsageText = "app [first_arg] [second_arg]"
	app.Usage = "Some app"
	app.Authors = []*Author{
		{Name: "Harrison", Email: "harrison@lolwut.com"},
		{Name: "Oliver Allen", Email: "oliver@toyshop.com"},
	}
	return app
}

func expectFileContent(t *testing.T, file, expected string) {
	data, err := ioutil.ReadFile(file)
	// Ignore windows line endings
	// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
	data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
	expect(t, err, nil)
	expect(t, string(data), expected)
}

func TestToMarkdownFull(t *testing.T) {
	// Given
	app := testApp()

	// When
	res, err := app.ToMarkdown()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-full.md", res)
}

func TestToMarkdownNoFlags(t *testing.T) {
	// Given
	app := testApp()
	app.Flags = nil

	// When
	res, err := app.ToMarkdown()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-no-flags.md", res)
}

func TestToMarkdownNoCommands(t *testing.T) {
	// Given
	app := testApp()
	app.Commands = nil

	// When
	res, err := app.ToMarkdown()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-no-commands.md", res)
}

func TestToMarkdownNoAuthors(t *testing.T) {
	// Given
	app := testApp()
	app.Authors = []*Author{}

	// When
	res, err := app.ToMarkdown()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-no-authors.md", res)
}

func TestToMan(t *testing.T) {
	// Given
	app := testApp()

	// When
	res, err := app.ToMan()

	// Then
	expect(t, err, nil)
	expectFileContent(t, "testdata/expected-doc-full.man", res)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/CleverbotIO/go-cleverbot.io"
	"github.com/PonyvilleFM/aura/pvfm/station"
	"github.com/bwmarrin/discordgo"
	"github.com/techniponi/doritobot/gitupdate"
)

// randomRange gives a random whole integer between the given integers [min, max)
func randomRange(min, max int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(max-min) + min
}

// Config is a json file containing the login data and any user-configurable values
type Config struct {
	Username            string `json:"username"`
	Password            string `json:"password"`
	Bot                 bool   `json:"Bot"`
	BotToken            string `json:"botToken"`
	HTTPEndpointEnabled bool   `json:"httpEndpointEnabled"`
	EchoCommandEnabled  bool   `json:"echoCommandEnabled"`
}

// DerpiResults is a struct to contain Derpibooru search results
type DerpiResults struct {
	Search []struct {
		ID               string        `json:"id"`
		CreatedAt        time.Time     `json:"created_at"`
		UpdatedAt        time.Time     `json:"updated_at"`
		DuplicateReports []interface{} `json:"duplicate_reports"`
		FirstSeenAt      time.Time     `json:"first_seen_at"`
		UploaderID       string        `json:"uploader_id"`
		Score            int           `json:"score"`
		CommentCount     int           `json:"comment_count"`
		Width            int           `json:"width"`
		Height           int           `json:"height"`
		FileName         string        `json:"file_name"`
		Description      string        `json:"description"`
		Uploader         string        `json:"uploader"`
		Image            string        `json:"image"`
		Upvotes          int           `json:"upvotes"`
		Downvotes        int           `json:"downvotes"`
		Faves            int           `json:"faves"`
		Tags             string        `json:"tags"`
		TagIds           []string      `json:"tag_ids"`
		AspectRatio      float64       `json:"aspect_ratio"`
		OriginalFormat   string        `json:"original_format"`
		MimeType         string        `json:"mime_type"`
		Sha512Hash       string        `json:"sha512_hash"`
		OrigSha512Hash   string        `json:"orig_sha512_hash"`
		SourceURL        string        `json:"source_url"`
		Representations  struct {
			ThumbTiny  string `json:"thumb_tiny"`
			ThumbSmall string `json:"thumb_small"`
			Thumb      string `json:"thumb"`
			Small      string `json:"small"`
			Medium     string `json:"medium"`
			Large      string `json:"large"`
			Tall       string `json:"tall"`
			Full       string `json:"full"`
		} `json:"representations"`
		IsRendered  bool `json:"is_rendered"`
		IsOptimized bool `json:"is_optimized"`
	} `json:"search"`
	Total        int           `json:"total"`
	Interactions []interface{} `json:"interactions"`
}

// These need to be global
var cb *cleverbot.Session
var botErr error
var currentID string
var userIDError error
var dg *discordgo.Session
var err error
var login Config

func main() {
	// Read the config.json
	loginFile, fileErr := ioutil.ReadFile("./config.json")
	if fileErr != nil {
		fmt.Println(fileErr.Error())
		os.Exit(1)
	}

	// Parse it
	jsonErr := json.Unmarshal(loginFile, &login)

	if jsonErr != nil {
		fmt.Println(jsonErr.Error())
		os.Exit(1)
	}

	// Log in to discord
	if login.Bot {
		dg, err = discordgo.New("Bot " + login.BotToken)
	} else {
		dg, err = discordgo.New(login.Username, login.Password)
	}

	if err != nil {
		fmt.Println("Error creating session: ", err)
		os.Exit(1)
	} else {
		fmt.Println("Successfully logged in.")
	}

	// Message received handler
	dg.AddHandler(messageCreate)

	err = dg.Open()
	if err != nil {
		fmt.Println("Error opening connection: ", err)
		os.Exit(1)
	}

	// Open cleverbot API
	// TODO: move this to login.json
	cb, botErr = cleverbot.New("JHowZe3ddT6Da0JU", "8NSK1vZVH1lRNMIcTbu4hU6kGEyIDxsW", "")
	if botErr != nil {
		log.Fatal(botErr)
	}

	// HTTP endpoint handler
	http.HandleFunc("/chat", func(w http.ResponseWriter, r *http.Request) {
		if login.HTTPEndpointEnabled {
			id := r.URL.Query().Get("id")
			msg := r.URL.Query().Get("msg")
			if id != "" && msg != "" {
				dg.ChannelMessageSend(id, "Message from HTTP chat endpoint: \n`"+msg+"`")
				fmt.Fprintf(w, "You are sending: \n"+msg+"\nto channel ID: \n"+id)
			}
		}
	})

	http.HandleFunc("/repoupdate", func(w http.ResponseWriter, r *http.Request) {
		decoder := json.NewDecoder(r.Body)
		var update gitupdate.GitUpdate
		err := decoder.Decode(&update)
		if err != nil {
			log.Fatal(err)
		}
		defer r.Body.Close()
		updateString := "New commit pushed to <https://github.com/techniponi/doritobot> (" + update.HeadCommit.ID + "):\n" + update.HeadCommit.Author.Name + ": " + update.HeadCommit.Message + "\n" + update.HeadCommit.URL

		dg.ChannelMessageSend("298642620849324035", updateString)
	})

	dgUser, userIDError := dg.User("@me")
	currentID = dgUser.ID

	if userIDError != nil {
		log.Fatal(userIDError)
	}

	fmt.Println("Bot is now running.  Press CTRL-C to exit.")
	log.Fatal(http.ListenAndServe(":8080", nil))
	<-make(chan struct{})
	return
}

// messageCreate is the handler function for all incoming Discord messages
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	var msg []string
	if m.Author.Username == "PonyChat" {
		// this is disabled since my implementation was SHIT
		//msg = strings.Split(m.Content[strings.Index(m.Content, ">")+4:len(m.Content)], " ")
	} else {
		msg = strings.Split(m.Content, " ")
	}

	if len(msg) > 0 {
		switch msg[0] {
		case "nazoupdate": // Reminds everyone that Nazo is adorable
			s.ChannelMessageDelete(m.ChannelID, m.ID)
			s.ChannelMessageSend(m.ChannelID, "This is your daily reminder that <@!165846085020024832> is adorable.")
			break
		case "echo": // Echoes the given text
			if login.EchoCommandEnabled {
				s.ChannelMessageDelete(m.ChannelID, m.ID)
				/*if m.Author.Username == "PonyChat" {
					s.ChannelMessageSend(m.ChannelID, "`ds:` "+m.Content[15:len(m.Content)])
				} else {*/
				s.ChannelMessageSend(m.ChannelID, "`echo:`\n"+m.Content[11:len(m.Content)])
				//}
			}
			break
		case "cb": // Communicates with Cleverbot.
			response, botErr2 := cb.Ask(m.Content[2:len(m.Content)])
			if botErr2 != nil {
				log.Fatal(botErr2)
			}
			s.ChannelMessageSend(m.ChannelID, response)
			break
		case "db": // Grabs an image from Derpibooru results with a given list of tags - always a safe image!
			if len(msg) < 2 {
				s.ChannelMessageSend(m.ChannelID, "Error: not enough arguments")
				break
			}
			derpiTags := strings.Replace(m.Content[3:len(m.Content)], " ", "+", -1)
			resp, derpiHTTPErr := http.Get("https://derpibooru.org/search.json?q=safe," + derpiTags)
			if derpiHTTPErr != nil {
				s.ChannelMessageSend(m.ChannelID, "HTTP error, check console")
				log.Fatal(derpiHTTPErr)
				break
			}
			defer resp.Body.Close()
			derpiBody, derpiErr := ioutil.ReadAll(resp.Body)
			var derpiErr2 error
			results := DerpiResults{}
			derpiErr2 = json.Unmarshal(derpiBody, &results)
			if derpiErr != nil {
				s.ChannelMessageSend(m.ChannelID, "Error reading HTTP response")
				break
			}
			if derpiErr2 != nil {
				s.ChannelMessageSend(m.ChannelID, "Error reading derpibooru API response")
				break
			}
			if len(results.Search) > 0 {
				s.ChannelMessageSend(m.ChannelID, "http:"+results.Search[randomRange(0, len(results.Search))].Image)
			} else {
				s.ChannelMessageSend(m.ChannelID, "Error: no results")
			}
			break
		case "h": // h
			if m.Author.ID != currentID {
				s.ChannelMessageSend(m.ChannelID, "h")
			}
			break
		case "pvfmservers": // Gives a list of all available PVFM streams (direct links)
			currentMeta, metaErr := station.GetStats()
			if metaErr != nil {
				s.ChannelMessageSend(m.ChannelID, "Error receiving pvfm metadata")
				break
			}

			outputString := "**PVFM Servers:**\n"

			for _, element := range currentMeta.Icestats.Source {
				outputString += ":musical_note: " + element.ServerDescription + ":\n`" + strings.Replace(element.Listenurl, "aerial", "dj.bronyradio.com", -1) + "`\n"
			}

			outputString += "\n:cd: DJ Recordings:\n`http://darkling.darkwizards.com/wang/BronyRadio/?M=D`"

			s.ChannelMessageSend(m.ChannelID, outputString)

			break
		}
	}
}
<commit_msg>Add techgore command<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/CleverbotIO/go-cleverbot.io"
	"github.com/PonyvilleFM/aura/pvfm/station"
	"github.com/bwmarrin/discordgo"
	"github.com/jzelinskie/geddit"
	"github.com/techniponi/doritobot/gitupdate"
)

// randomRange gives a random whole integer between the given integers [min, max)
func randomRange(min, max int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(max-min) + min
}

// Config is a json file containing the login data and any user-configurable values
type Config struct {
	Username            string `json:"username"`
	Password            string `json:"password"`
	Bot                 bool   `json:"Bot"`
	BotToken            string `json:"botToken"`
	HTTPEndpointEnabled bool   `json:"httpEndpointEnabled"`
	EchoCommandEnabled  bool   `json:"echoCommandEnabled"`
}

// DerpiResults is a struct to contain Derpibooru search results
type DerpiResults struct {
	Search []struct {
		ID               string        `json:"id"`
		CreatedAt        time.Time     `json:"created_at"`
		UpdatedAt        time.Time     `json:"updated_at"`
		DuplicateReports []interface{} `json:"duplicate_reports"`
		FirstSeenAt      time.Time     `json:"first_seen_at"`
		UploaderID       string        `json:"uploader_id"`
		Score            int           `json:"score"`
		CommentCount     int           `json:"comment_count"`
		Width            int           `json:"width"`
		Height           int           `json:"height"`
		FileName         string        `json:"file_name"`
		Description      string        `json:"description"`
		Uploader         string        `json:"uploader"`
		Image            string        `json:"image"`
		Upvotes          int           `json:"upvotes"`
		Downvotes        int           `json:"downvotes"`
		Faves            int           `json:"faves"`
		Tags             string        `json:"tags"`
		TagIds           []string      `json:"tag_ids"`
		AspectRatio      float64       `json:"aspect_ratio"`
		OriginalFormat   string        `json:"original_format"`
		MimeType         string        `json:"mime_type"`
		Sha512Hash       string        `json:"sha512_hash"`
		OrigSha512Hash   string        `json:"orig_sha512_hash"`
		SourceURL        string        `json:"source_url"`
		Representations  struct {
			ThumbTiny  string `json:"thumb_tiny"`
			ThumbSmall string `json:"thumb_small"`
			Thumb      string `json:"thumb"`
			Small      string `json:"small"`
			Medium     string `json:"medium"`
			Large      string `json:"large"`
			Tall       string `json:"tall"`
			Full       string `json:"full"`
		} `json:"representations"`
		IsRendered  bool `json:"is_rendered"`
		IsOptimized bool `json:"is_optimized"`
	} `json:"search"`
	Total        int           `json:"total"`
	Interactions []interface{} `json:"interactions"`
}

// These need to be global
var cb *cleverbot.Session
var botErr error
var currentID string
var userIDError error
var dg *discordgo.Session
var err error
var login Config

func main() {
	// Read the config.json
	loginFile, fileErr := ioutil.ReadFile("./config.json")
	if fileErr != nil {
		fmt.Println(fileErr.Error())
		os.Exit(1)
	}

	// Parse it
	jsonErr := json.Unmarshal(loginFile, &login)

	if jsonErr != nil {
		fmt.Println(jsonErr.Error())
		os.Exit(1)
	}

	// Log in to discord
	if login.Bot {
		dg, err = discordgo.New("Bot " + login.BotToken)
	} else {
		dg, err = discordgo.New(login.Username, login.Password)
	}

	if err != nil {
		fmt.Println("Error creating session: ", err)
		os.Exit(1)
	} else {
		fmt.Println("Successfully logged in.")
	}

	// Message received handler
	dg.AddHandler(messageCreate)

	err = dg.Open()
	if err != nil {
		fmt.Println("Error opening connection: ", err)
		os.Exit(1)
	}

	// Open cleverbot API
	// TODO: move this to login.json
	cb, botErr = cleverbot.New("JHowZe3ddT6Da0JU", "8NSK1vZVH1lRNMIcTbu4hU6kGEyIDxsW", "")
	if botErr != nil {
		log.Fatal(botErr)
	}

	// HTTP endpoint handler
	http.HandleFunc("/chat", func(w http.ResponseWriter, r *http.Request) {
		if login.HTTPEndpointEnabled {
			id := r.URL.Query().Get("id")
			msg := r.URL.Query().Get("msg")
			if id != "" && msg != "" {
				dg.ChannelMessageSend(id, "Message from HTTP chat endpoint: \n`"+msg+"`")
				fmt.Fprintf(w, "You are sending: \n"+msg+"\nto channel ID: \n"+id)
			}
		}
	})

	http.HandleFunc("/repoupdate", func(w http.ResponseWriter, r *http.Request) {
		decoder := json.NewDecoder(r.Body)
		var update gitupdate.GitUpdate
		err := decoder.Decode(&update)
		if err != nil {
			log.Fatal(err)
		}
		defer r.Body.Close()
		updateString := "New commit pushed to <https://github.com/techniponi/doritobot> (" + update.HeadCommit.ID + "):\n" + update.HeadCommit.Author.Name + ": " + update.HeadCommit.Message + "\n" + update.HeadCommit.URL

		dg.ChannelMessageSend("298642620849324035", updateString)
	})

	dgUser, userIDError := dg.User("@me")
	currentID = dgUser.ID

	if userIDError != nil {
		log.Fatal(userIDError)
	}

	fmt.Println("Bot is now running.  Press CTRL-C to exit.")
	log.Fatal(http.ListenAndServe(":8080", nil))
	<-make(chan struct{})
	return
}

// messageCreate is the handler function for all incoming Discord messages
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	var msg []string
	if m.Author.Username == "PonyChat" {
		// this is disabled since my implementation was SHIT
		//msg = strings.Split(m.Content[strings.Index(m.Content, ">")+4:len(m.Content)], " ")
	} else {
		msg = strings.Split(m.Content, " ")
	}

	if len(msg) > 0 {
		switch msg[0] {
		case "nazoupdate": // Reminds everyone that Nazo is adorable
			s.ChannelMessageDelete(m.ChannelID, m.ID)
			s.ChannelMessageSend(m.ChannelID, "This is your daily reminder that <@!165846085020024832> is adorable.")
			break
		case "echo": // Echoes the given text
			if login.EchoCommandEnabled {
				s.ChannelMessageDelete(m.ChannelID, m.ID)
				/*if m.Author.Username == "PonyChat" {
					s.ChannelMessageSend(m.ChannelID, "`ds:` "+m.Content[15:len(m.Content)])
				} else {*/
				s.ChannelMessageSend(m.ChannelID, "`echo:`\n"+m.Content[11:len(m.Content)])
				//}
			}
			break
		case "cb": // Communicates with Cleverbot.
			response, botErr2 := cb.Ask(m.Content[2:len(m.Content)])
			if botErr2 != nil {
				log.Fatal(botErr2)
			}
			s.ChannelMessageSend(m.ChannelID, response)
			break
		case "db": // Grabs an image from Derpibooru results with a given list of tags - always a safe image!
			if len(msg) < 2 {
				s.ChannelMessageSend(m.ChannelID, "Error: not enough arguments")
				break
			}
			derpiTags := strings.Replace(m.Content[3:len(m.Content)], " ", "+", -1)
			resp, derpiHTTPErr := http.Get("https://derpibooru.org/search.json?q=safe," + derpiTags)
			if derpiHTTPErr != nil {
				s.ChannelMessageSend(m.ChannelID, "HTTP error, check console")
				log.Fatal(derpiHTTPErr)
				break
			}
			defer resp.Body.Close()
			derpiBody, derpiErr := ioutil.ReadAll(resp.Body)
			var derpiErr2 error
			results := DerpiResults{}
			derpiErr2 = json.Unmarshal(derpiBody, &results)
			if derpiErr != nil {
				s.ChannelMessageSend(m.ChannelID, "Error reading HTTP response")
				break
			}
			if derpiErr2 != nil {
				s.ChannelMessageSend(m.ChannelID, "Error reading derpibooru API response")
				break
			}
			if len(results.Search) > 0 {
				s.ChannelMessageSend(m.ChannelID, "http:"+results.Search[randomRange(0, len(results.Search))].Image)
			} else {
				s.ChannelMessageSend(m.ChannelID, "Error: no results")
			}
			break
		case "h": // h
			if m.Author.ID != currentID {
				s.ChannelMessageSend(m.ChannelID, "h")
			}
			break
		case "pvfmservers": // Gives a list of all available PVFM streams (direct links)
			currentMeta, metaErr := station.GetStats()
			if metaErr != nil {
				s.ChannelMessageSend(m.ChannelID, "Error receiving pvfm metadata")
				break
			}

			outputString := "**PVFM Servers:**\n"

			for _, element := range currentMeta.Icestats.Source {
				outputString += ":musical_note: " + element.ServerDescription + ":\n`" + strings.Replace(element.Listenurl, "aerial", "dj.bronyradio.com", -1) + "`\n"
			}

			outputString += "\n:cd: DJ Recordings:\n`http://darkling.darkwizards.com/wang/BronyRadio/?M=D`"

			s.ChannelMessageSend(m.ChannelID, outputString)

			break
		case "techgore":
			listOptions := geddit.ListingOptions{
				Limit: 50,
			}
			reddit := geddit.NewSession("discordbot")
			results, err := reddit.SubredditSubmissions("techsupportgore", geddit.NewSubmissions, listOptions)
			if err != nil {
				break
			}
			s.ChannelMessageSend(m.ChannelID, results[randomRange(0, len(results))].URL)
			break
		}
	}
}
<|endoftext|>


<commit_before>package pattern

import (
	"regexp"
)

var (
	Numeric              = New(`^(\d+)$`)
	AlphaNumeric         = New("^([0-9A-Za-z]+)$")
	Alpha                = New("^([A-Za-z]+)$")
	AlphaCapsOnly        = New("^([A-Z]+)$")
	AlphaNumericCapsOnly = New("^([0-9A-Z]+)$")
	Url                  = New(`^((https?|ftps?):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$`)
	Email                = New(`^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$`)
	HashtagHex           = New(`^#([a-f0-9]{6}|[a-f0-9]{3})$`)
	ZeroXHex             = New(`^0x([a-f0-9]+|[A-F0-9]+)$`)
	IPv4                 = New(`^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`)
	IPv6                 = New(`^([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{1,4}$|((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4})$`)
)

// Represents the General pattern type
type Pattern struct {
	pat string
	reg *regexp.Regexp
}

func New(pat string) *Pattern {
	p := &Pattern{pat: pat}
	p.reg = regexp.MustCompile(p.pat)
	return p
}

// Returns true if s matches the compiled regex pattern
func (p *Pattern) Match(s string) bool {
	return p.reg.MatchString(s)
}
<commit_msg>Changed Email pattern to match more generic input format, fix #1<commit_after>package pattern

import (
	"regexp"
)

var (
	Numeric              = New(`^(\d+)$`)
	AlphaNumeric         = New("^([0-9A-Za-z]+)$")
	Alpha                = New("^([A-Za-z]+)$")
	AlphaCapsOnly        = New("^([A-Z]+)$")
	AlphaNumericCapsOnly = New("^([0-9A-Z]+)$")
	Url                  = New(`^((https?|ftps?):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$`)
	Email                = New(`^(.+@([\da-z\.-]+)\.([a-z\.]{2,6}))$`)
	HashtagHex           = New(`^#([a-f0-9]{6}|[a-f0-9]{3})$`)
	ZeroXHex             = New(`^0x([a-f0-9]+|[A-F0-9]+)$`)
	IPv4                 = New(`^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`)
	IPv6                 = New(`^([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{1,4}$|((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4})$`)
)

// Represents the General pattern type
type Pattern struct {
	pat string
	reg *regexp.Regexp
}

func New(pat string) *Pattern {
	p := &Pattern{pat: pat}
	p.reg = regexp.MustCompile(p.pat)
	return p
}

// Returns true if s matches the compiled regex pattern
func (p *Pattern) Match(s string) bool {
	return p.reg.MatchString(s)
}
<|endoftext|>