<commit_before>// Package g11n is an internationalization library that offers:
//
//	* Statically-typed message keys.
//	* Parameterized messages.
//	* Extendable message formatting.
//	* Custom localization file format.
//
//
// I. Initialization
//
// Create a new instance of g11n. Each instance handles messages and locales separately.
//
//	G = g11n.New()
//
// Define a struct with messages.
//
//	type Messages struct {
//		TheAnswer func(string, int) string `default:"The answer to %v is %v."`
//	}
//
// Initialize an instance of the struct through the g11n object.
//
//	var M *Messages
//	G.Init(M)
//
// Invoke messages on that instance.
//
//	M.TheAnswer("everything", 42")
//
//
// II. Choosing locale
//
// Specify the locale for every message struct initialized by this g11n instance.
//
//	G.SetLocale("en")
//
//
// III. Format parameters
//
// The parameters of a message call could be formatted by declaring a special
// type that implements
//	G11nParam() string
// The format method G11nParam is invoked before substituting a parameter in the message.
//
//
//	type PluralFormat int
//
//	func (pf PluralFormat) G11nParam() string {
//		switch pf {
//		case 0:
//			return "some"
//		case 1:
//			return "crazy"
//		default:
//			return "stuff"
//		}
//	}
//
//	type M struct {
//		MyLittleSomething func(PluralFormat) string `default:"Count: %v"`
//	}
//
//
// IV. Format result
//
// The result of a message call could be further formatted by declaring a special
// result type that implements
//	G11nResult(formattedMessage string) string
// The format method G11nResult is invoked after all parameters have been substituted in the message.
//
//	type SafeHTMLFormat string
//
//	func (shf SafeHTMLFormat) G11nResult(formattedMessage string) string {
//		r := strings.NewReplacer("<", `\<`, ">", `\>`, "/", `\/`)
//		return r.Replace(formattedMessage)
//	}
//
//	type M struct {
//		MyLittleSomething func() SafeHTMLFormat `default:"<message>Oops!</message>"`
//	}
package g11n
<commit_msg>Add locale loading doc<commit_after>// Package g11n is an internationalization library that offers:
//
//	* Statically-typed message keys.
//	* Parameterized messages.
//	* Extendable message formatting.
//	* Custom localization file format.
//
//
// I. Initialization
//
// Create a new instance of g11n. Each instance handles messages and locales separately.
//
//	G = g11n.New()
//
// Define a struct with messages.
//
//	type Messages struct {
//		TheAnswer func(string, int) string `default:"The answer to %v is %v."`
//	}
//
// Initialize an instance of the struct through the g11n object.
//
//	var M *Messages
//	G.Init(M)
//
// Invoke messages on that instance.
//
//	M.TheAnswer("everything", 42")
//
//
// II. Choosing locale
//
// Load a locale in the g11n instance.
//
//	G.LoadLocale("json", "bg", bgLocalePath)
//
// Different locale loaders could be registered by implementing the locale.Loader
// interface.
//
// Specify the locale for every message struct initialized by this g11n instance.
//
//	G.SetLocale("en")
//
//
// III. Format parameters
//
// The parameters of a message call could be formatted by declaring a special
// type that implements
//	G11nParam() string
// The format method G11nParam is invoked before substituting a parameter in the message.
//
//
//	type PluralFormat int
//
//	func (pf PluralFormat) G11nParam() string {
//		switch pf {
//		case 0:
//			return "some"
//		case 1:
//			return "crazy"
//		default:
//			return "stuff"
//		}
//	}
//
//	type M struct {
//		MyLittleSomething func(PluralFormat) string `default:"Count: %v"`
//	}
//
//
// IV. Format result
//
// The result of a message call could be further formatted by declaring a special
// result type that implements
//	G11nResult(formattedMessage string) string
// The format method G11nResult is invoked after all parameters have been substituted in the message.
//
//	type SafeHTMLFormat string
//
//	func (shf SafeHTMLFormat) G11nResult(formattedMessage string) string {
//		r := strings.NewReplacer("<", `\<`, ">", `\>`, "/", `\/`)
//		return r.Replace(formattedMessage)
//	}
//
//	type M struct {
//		MyLittleSomething func() SafeHTMLFormat `default:"<message>Oops!</message>"`
//	}
package g11n
<|endoftext|>


<commit_before>package bimg

/*
#cgo pkg-config: vips
#include "vips/vips.h"
*/
import "C"

import (
	"errors"
	"math"
)

func Resize(buf []byte, o Options) ([]byte, error) {
	defer C.vips_thread_shutdown()

	if len(buf) == 0 {
		return nil, errors.New("Image buffer is empty")
	}

	image, imageType, err := vipsRead(buf)
	if err != nil {
		return nil, err
	}

	// Defaults
	if o.Quality == 0 {
		o.Quality = QUALITY
	}
	if o.Compression == 0 {
		o.Compression = 6
	}
	if o.Type == 0 {
		o.Type = imageType
	}

	if IsTypeSupported(o.Type) == false {
		return nil, errors.New("Unsupported image output type")
	}

	debug("Options: %#v", o)

	inWidth := int(image.Xsize)
	inHeight := int(image.Ysize)

	// image calculations
	factor := imageCalculations(&o, inWidth, inHeight)
	shrink := int(math.Max(math.Floor(factor), 1))
	residual := float64(shrink) / factor

	// Calculate integral box shrink
	windowSize := vipsWindowSize(o.Interpolator.String())
	if factor >= 2 && windowSize > 3 {
		// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
		shrink = int(math.Max(float64(math.Floor(factor*3.0/windowSize)), 1))
	}

	// Do not enlarge the output if the input width *or* height are already less than the required dimensions
	if o.Enlarge == false {
		if inWidth < o.Width && inHeight < o.Height {
			factor = 1.0
			shrink = 1
			residual = 0
			o.Width = inWidth
			o.Height = inHeight
		}
	}

	// Try to use libjpeg shrink-on-load
	if imageType == JPEG && shrink >= 2 {
		tmpImage, factor, err := shrinkJpegImage(buf, image, factor, shrink)
		if err != nil {
			return nil, err
		}
		image = tmpImage
		factor = math.Max(factor, 1.0)
		shrink = int(math.Floor(factor))
		residual = float64(shrink) / factor
	}

	// Zoom image if necessary
	image, err = zoomImage(image, o.Zoom)
	if err != nil {
		return nil, err
	}

	// Rotate / flip image if necessary
	image, err = rotateAndFlipImage(image, o)
	if err != nil {
		return nil, err
	}

	// Transform image if necessary
	shouldTransform := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0
	if shouldTransform {

		// Use vips_shrink with the integral reduction
		if shrink > 1 {
			image, residual, err = shrinkImage(image, o, residual, shrink)
			if err != nil {
				return nil, err
			}
		}

		// Affine with the remaining float part
		if residual != 0 {
			image, err = vipsAffine(image, residual, o.Interpolator)
			if err != nil {
				return nil, err
			}
		}

		debug("Transform image: factor=%v, shrink=%v, residual=%v", factor, shrink, residual)
		// Extract area from image
		image, err = extractImage(image, o)
		if err != nil {
			return nil, err
		}
	}

	// Add watermark if necessary
	image, err = watermakImage(image, o.Watermark)
	if err != nil {
		return nil, err
	}

	saveOptions := vipsSaveOptions{
		Quality:     o.Quality,
		Type:        o.Type,
		Compression: o.Compression,
	}

	// Finally save as buffer
	buf, err = vipsSave(image, saveOptions)
	if err != nil {
		return nil, err
	}

	return buf, nil
}

func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) {
	var err error = nil
	inWidth := int(image.Xsize)
	inHeight := int(image.Ysize)

	switch {
	case o.Crop:
		width := int(math.Min(float64(inWidth), float64(o.Width)))
		height := int(math.Min(float64(inHeight), float64(o.Height)))
		left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity)
		image, err = vipsExtract(image, left, top, width, height)
		break
	case o.Embed:
		left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2
		image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend)
		break
	case o.Top > 0 || o.Left > 0:
		if o.AreaWidth == 0 || o.AreaHeight == 0 {
			err = errors.New("Area to extract cannot be 0")
		} else {
			image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
		}
		break
	}

	return image, err
}

func rotateAndFlipImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) {
	var err error
	var direction Direction = -1

	if o.NoAutoRotate == false {
		rotation, flip := calculateRotationAndFlip(image, o.Rotate)
		if flip {
			o.Flip = flip
		}
		if rotation > D0 && o.Rotate == 0 {
			o.Rotate = rotation
		}
	}

	if o.Rotate > 0 {
		image, err = vipsRotate(image, getAngle(o.Rotate))
	}

	if o.Flip {
		direction = HORIZONTAL
	} else if o.Flop {
		direction = VERTICAL
	}

	if direction != -1 {
		image, err = vipsFlip(image, direction)
	}

	return image, err
}

func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) {
	if w.Text == "" {
		return image, nil
	}

	// Defaults
	if w.Font == "" {
		w.Font = "sans 10"
	}
	if w.Width == 0 {
		w.Width = int(math.Floor(float64(image.Xsize / 6)))
	}
	if w.DPI == 0 {
		w.DPI = 150
	}
	if w.Margin == 0 {
		w.Margin = w.Width
	}
	if w.Opacity == 0 {
		w.Opacity = 0.25
	} else if w.Opacity > 1 {
		w.Opacity = 1
	}

	image, err := vipsWatermark(image, w)
	if err != nil {
		return nil, err
	}

	return image, nil
}

func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) {
	if zoom == 0 {
		return image, nil
	}

	return vipsZoom(image, zoom+1)
}

func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink int) (*C.struct__VipsImage, float64, error) {
	// Use vips_shrink with the integral reduction
	image, err := vipsShrink(image, shrink)
	if err != nil {
		return nil, 0, err
	}

	// Recalculate residual float based on dimensions of required vs shrunk images
	residualx := float64(o.Width) / float64(image.Xsize)
	residualy := float64(o.Height) / float64(image.Ysize)

	if o.Crop {
		residual = math.Max(residualx, residualy)
	} else {
		residual = math.Min(residualx, residualy)
	}

	return image, residual, nil
}

func shrinkJpegImage(buf []byte, input *C.struct__VipsImage, factor float64, shrink int) (*C.struct__VipsImage, float64, error) {
	var image *C.struct__VipsImage
	var err error
	shrinkOnLoad := 1

	// Recalculate integral shrink and double residual
	switch {
	case shrink >= 8:
		factor = factor / 8
		shrinkOnLoad = 8
	case shrink >= 4:
		factor = factor / 4
		shrinkOnLoad = 4
	case shrink >= 2:
		factor = factor / 2
		shrinkOnLoad = 2
	}

	// Reload input using shrink-on-load
	if shrinkOnLoad > 1 {
		image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad)
	}

	return image, factor, err
}

func imageCalculations(o *Options, inWidth, inHeight int) float64 {
	factor := 1.0
	xfactor := float64(inWidth) / float64(o.Width)
	yfactor := float64(inHeight) / float64(o.Height)

	switch {
	// Fixed width and height
	case o.Width > 0 && o.Height > 0:
		if o.Crop {
			factor = math.Min(xfactor, yfactor)
		} else {
			factor = math.Max(xfactor, yfactor)
		}
	// Fixed width, auto height
	case o.Width > 0:
		factor = xfactor
		o.Height = int(math.Floor(float64(inHeight) / factor))
	// Fixed height, auto width
	case o.Height > 0:
		factor = yfactor
		o.Width = int(math.Floor(float64(inWidth) / factor))
	default:
		// Identity transform
		o.Width = inWidth
		o.Height = inHeight
		break
	}

	return factor
}

func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) (int, int) {
	left, top := 0, 0

	switch gravity {
	case NORTH:
		left = (inWidth - outWidth + 1) / 2
	case EAST:
		left = inWidth - outWidth
		top = (inHeight - outHeight + 1) / 2
	case SOUTH:
		left = (inWidth - outWidth + 1) / 2
		top = inHeight - outHeight
	case WEST:
		top = (inHeight - outHeight + 1) / 2
	default:
		left = (inWidth - outWidth + 1) / 2
		top = (inHeight - outHeight + 1) / 2
	}

	return left, top
}

func calculateRotationAndFlip(image *C.struct__VipsImage, angle Angle) (Angle, bool) {
	rotate := D0
	flip := false

	if angle > 0 {
		return rotate, flip
	}

	switch vipsExifOrientation(image) {
	case 6:
		rotate = D90
		break
	case 3:
		rotate = D180
		break
	case 8:
		rotate = D270
		break
	case 2:
		flip = true
		break // flip 1
	case 7:
		flip = true
		rotate = D90
		break // flip 6
	case 4:
		flip = true
		rotate = D180
		break // flip 3
	case 5:
		flip = true
		rotate = D270
		break // flip 8
	}

	return rotate, flip
}

func getAngle(angle Angle) Angle {
	divisor := angle % 90
	if divisor != 0 {
		angle = angle - divisor
	}
	return Angle(math.Min(float64(angle), 270))
}
<commit_msg>fix(#32): bad crop<commit_after>package bimg

/*
#cgo pkg-config: vips
#include "vips/vips.h"
*/
import "C"

import (
	"errors"
	"math"
)

func Resize(buf []byte, o Options) ([]byte, error) {
	defer C.vips_thread_shutdown()

	if len(buf) == 0 {
		return nil, errors.New("Image buffer is empty")
	}

	image, imageType, err := vipsRead(buf)
	if err != nil {
		return nil, err
	}

	// Defaults
	if o.Quality == 0 {
		o.Quality = QUALITY
	}
	if o.Compression == 0 {
		o.Compression = 6
	}
	if o.Type == 0 {
		o.Type = imageType
	}

	if IsTypeSupported(o.Type) == false {
		return nil, errors.New("Unsupported image output type")
	}

	debug("Options: %#v", o)

	inWidth := int(image.Xsize)
	inHeight := int(image.Ysize)

	// image calculations
	factor := imageCalculations(&o, inWidth, inHeight)
	shrink := calculateShrink(factor, o.Interpolator)
	residual := calculateResidual(factor, shrink)

	// Do not enlarge the output if the input width *or* height are already less than the required dimensions
	if o.Enlarge == false {
		if inWidth < o.Width && inHeight < o.Height {
			factor = 1.0
			shrink = 1
			residual = 0
			o.Width = inWidth
			o.Height = inHeight
		}
	}

	// Try to use libjpeg shrink-on-load
	if imageType == JPEG && shrink >= 2 {
		tmpImage, factor, err := shrinkJpegImage(buf, image, factor, shrink)
		if err != nil {
			return nil, err
		}

		image = tmpImage
		factor = math.Max(factor, 1.0)
		shrink = int(math.Floor(factor))
		residual = float64(shrink) / factor
	}

	// Zoom image if necessary
	image, err = zoomImage(image, o.Zoom)
	if err != nil {
		return nil, err
	}

	// Rotate / flip image if necessary
	image, err = rotateAndFlipImage(image, o)
	if err != nil {
		return nil, err
	}

	// Transform image if necessary
	shouldTransform := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0
	if shouldTransform {

		// Use vips_shrink with the integral reduction
		if shrink > 1 {
			image, residual, err = shrinkImage(image, o, residual, shrink)
			if err != nil {
				return nil, err
			}
		}

		// Affine with the remaining float part
		if residual != 0 {
			image, err = vipsAffine(image, residual, o.Interpolator)
			if err != nil {
				return nil, err
			}
		}

		// Extract area from image
		image, err = extractImage(image, o)
		if err != nil {
			return nil, err
		}

		debug("Transform: factor=%v, shrink=%v, residual=%v, interpolator=%v",
			factor, shrink, residual, o.Interpolator.String())
	}

	// Add watermark if necessary
	image, err = watermakImage(image, o.Watermark)
	if err != nil {
		return nil, err
	}

	saveOptions := vipsSaveOptions{
		Quality:     o.Quality,
		Type:        o.Type,
		Compression: o.Compression,
	}

	// Finally save as buffer
	buf, err = vipsSave(image, saveOptions)
	if err != nil {
		return nil, err
	}

	return buf, nil
}

func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) {
	var err error = nil
	inWidth := int(image.Xsize)
	inHeight := int(image.Ysize)

	switch {
	case o.Crop:
		width := int(math.Min(float64(inWidth), float64(o.Width)))
		height := int(math.Min(float64(inHeight), float64(o.Height)))
		left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity)
		image, err = vipsExtract(image, left, top, width, height)
		break
	case o.Embed:
		left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2
		image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend)
		break
	case o.Top > 0 || o.Left > 0:
		if o.AreaWidth == 0 {
			o.AreaHeight = o.Width
		}
		if o.AreaHeight == 0 {
			o.AreaHeight = o.Height
		}
		if o.AreaWidth == 0 || o.AreaHeight == 0 {
			return nil, errors.New("Extract area width/height is required")
		}
		image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
		break
	}

	return image, err
}

func rotateAndFlipImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) {
	var err error
	var direction Direction = -1

	if o.NoAutoRotate == false {
		rotation, flip := calculateRotationAndFlip(image, o.Rotate)
		if flip {
			o.Flip = flip
		}
		if rotation > D0 && o.Rotate == 0 {
			o.Rotate = rotation
		}
	}

	if o.Rotate > 0 {
		image, err = vipsRotate(image, getAngle(o.Rotate))
	}

	if o.Flip {
		direction = HORIZONTAL
	} else if o.Flop {
		direction = VERTICAL
	}

	if direction != -1 {
		image, err = vipsFlip(image, direction)
	}

	return image, err
}

func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) {
	if w.Text == "" {
		return image, nil
	}

	// Defaults
	if w.Font == "" {
		w.Font = "sans 10"
	}
	if w.Width == 0 {
		w.Width = int(math.Floor(float64(image.Xsize / 6)))
	}
	if w.DPI == 0 {
		w.DPI = 150
	}
	if w.Margin == 0 {
		w.Margin = w.Width
	}
	if w.Opacity == 0 {
		w.Opacity = 0.25
	} else if w.Opacity > 1 {
		w.Opacity = 1
	}

	image, err := vipsWatermark(image, w)
	if err != nil {
		return nil, err
	}

	return image, nil
}

func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) {
	if zoom == 0 {
		return image, nil
	}

	return vipsZoom(image, zoom+1)
}

func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink int) (*C.struct__VipsImage, float64, error) {
	// Use vips_shrink with the integral reduction
	image, err := vipsShrink(image, shrink)
	if err != nil {
		return nil, 0, err
	}

	// Recalculate residual float based on dimensions of required vs shrunk images
	residualx := float64(o.Width) / float64(image.Xsize)
	residualy := float64(o.Height) / float64(image.Ysize)

	if o.Crop {
		residual = math.Max(residualx, residualy)
	} else {
		residual = math.Min(residualx, residualy)
	}

	return image, residual, nil
}

func shrinkJpegImage(buf []byte, input *C.struct__VipsImage, factor float64, shrink int) (*C.struct__VipsImage, float64, error) {
	var image *C.struct__VipsImage
	var err error
	shrinkOnLoad := 1

	// Recalculate integral shrink and double residual
	switch {
	case shrink >= 8:
		factor = factor / 8
		shrinkOnLoad = 8
	case shrink >= 4:
		factor = factor / 4
		shrinkOnLoad = 4
	case shrink >= 2:
		factor = factor / 2
		shrinkOnLoad = 2
	}

	// Reload input using shrink-on-load
	if shrinkOnLoad > 1 {
		image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad)
	}

	return image, factor, err
}

func imageCalculations(o *Options, inWidth, inHeight int) float64 {
	factor := 1.0
	xfactor := float64(inWidth) / float64(o.Width)
	yfactor := float64(inHeight) / float64(o.Height)

	switch {
	// Fixed width and height
	case o.Width > 0 && o.Height > 0:
		if o.Crop {
			factor = math.Min(xfactor, yfactor)
		} else {
			factor = math.Max(xfactor, yfactor)
		}
	// Fixed width, auto height
	case o.Width > 0:
		factor = xfactor
		o.Height = int(math.Floor(float64(inHeight) / factor))
	// Fixed height, auto width
	case o.Height > 0:
		factor = yfactor
		o.Width = int(math.Floor(float64(inWidth) / factor))
	default:
		// Identity transform
		o.Width = inWidth
		o.Height = inHeight
		break
	}

	return factor
}

func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) (int, int) {
	left, top := 0, 0

	switch gravity {
	case NORTH:
		left = (inWidth - outWidth + 1) / 2
	case EAST:
		left = inWidth - outWidth
		top = (inHeight - outHeight + 1) / 2
	case SOUTH:
		left = (inWidth - outWidth + 1) / 2
		top = inHeight - outHeight
	case WEST:
		top = (inHeight - outHeight + 1) / 2
	default:
		left = (inWidth - outWidth + 1) / 2
		top = (inHeight - outHeight + 1) / 2
	}

	return left, top
}

func calculateRotationAndFlip(image *C.struct__VipsImage, angle Angle) (Angle, bool) {
	rotate := D0
	flip := false

	if angle > 0 {
		return rotate, flip
	}

	switch vipsExifOrientation(image) {
	case 6:
		rotate = D90
		break
	case 3:
		rotate = D180
		break
	case 8:
		rotate = D270
		break
	case 2:
		flip = true
		break // flip 1
	case 7:
		flip = true
		rotate = D90
		break // flip 6
	case 4:
		flip = true
		rotate = D180
		break // flip 3
	case 5:
		flip = true
		rotate = D270
		break // flip 8
	}

	return rotate, flip
}

func calculateShrink(factor float64, i Interpolator) int {
	var shrink float64

	// Calculate integral box shrink
	windowSize := vipsWindowSize(i.String())
	if factor >= 2 && windowSize > 3 {
		// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
		shrink = float64(math.Floor(factor * 3.0 / windowSize))
	} else {
		shrink = math.Floor(factor)
	}

	return int(math.Max(shrink, 1))
}

func calculateResidual(factor float64, shrink int) float64 {
	return float64(shrink) / factor
}

func getAngle(angle Angle) Angle {
	divisor := angle % 90
	if divisor != 0 {
		angle = angle - divisor
	}
	return Angle(math.Min(float64(angle), 270))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/cfpb/rhobot/database"
	"github.com/cfpb/rhobot/gocd"
	"github.com/cfpb/rhobot/healthcheck"
	"github.com/codegangsta/cli"
)

func main() {

	app := cli.NewApp()
	app.Name = "Rhobot"
	app.Usage = "Rhobot is your friend."
	app.EnableBashCompletion = true

	app.Commands = []cli.Command{
		{
			Name:    "run",
			Aliases: []string{},
			Usage:   "healthchecks|gocd|tbd",
			Subcommands: []cli.Command{
				{
					Name:  "healthchecks",
					Usage: "[database uri] [path to healthcheck file]",
					Action: func(c *cli.Context) {
						dburi := c.Args()[0]
						path := c.Args()[1]
						fmt.Println("DB_URI: ", dburi)
						fmt.Println("PATH: ", path)

						healthChecks := healthcheck.ReadYamlFromFile(path)
						cxn := database.GetPGConnection(dburi)
						healthcheck.PreformHealthChecks(healthChecks, cxn)
						//TODO: turn results into report
					},
				},
				{
					Name:    "gocd",
					Aliases: []string{},
					Usage:   "Interact with GoCD pipeline",
					Subcommands: []cli.Command{
						{
							Name:  "push",
							Usage: "[gocd host] [path to pipeline config] [pipeline group]",
							Action: func(c *cli.Context) {
								gocdhost := c.Args()[0]
								path := c.Args()[1]
								group := c.Args()[2]

								gocd.Push(gocdhost, path, group)

							},
						},
						{
							Name:  "pull",
							Usage: "[gocd host] [path to pipeline config]",
							Action: func(c *cli.Context) {
								gocdhost := c.Args()[0]
								path := c.Args()[1]

								gocd.Pull(gocdhost, path)
							},
						},
						{
							Name:  "clone",
							Usage: "[gocd host] [path to pipeline config] [pipeline name]",
							Action: func(c *cli.Context) {
								gocdhost := c.Args()[0]
								path := c.Args()[1]
								name := c.Args()[2]

								gocd.Clone(gocdhost, path, name)
							},
						},
					},
				},
			},
		},
	}

	app.Run(os.Args)
}
<commit_msg>group can be missing. Call Get() for argument instead of []<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/cfpb/rhobot/database"
	"github.com/cfpb/rhobot/gocd"
	"github.com/cfpb/rhobot/healthcheck"
	"github.com/codegangsta/cli"
)

func main() {

	app := cli.NewApp()
	app.Name = "Rhobot"
	app.Usage = "Rhobot is your friend."
	app.EnableBashCompletion = true

	app.Commands = []cli.Command{
		{
			Name:    "run",
			Aliases: []string{},
			Usage:   "healthchecks|gocd|tbd",
			Subcommands: []cli.Command{
				{
					Name:  "healthchecks",
					Usage: "[database uri] [path to healthcheck file]",
					Action: func(c *cli.Context) {
						dburi := c.Args()[0]
						path := c.Args()[1]
						fmt.Println("DB_URI: ", dburi)
						fmt.Println("PATH: ", path)

						healthChecks := healthcheck.ReadYamlFromFile(path)
						cxn := database.GetPGConnection(dburi)
						healthcheck.PreformHealthChecks(healthChecks, cxn)
						//TODO: turn results into report
					},
				},
				{
					Name:    "gocd",
					Aliases: []string{},
					Usage:   "Interact with GoCD pipeline",
					Subcommands: []cli.Command{
						{
							Name:  "push",
							Usage: "[gocd host] [path to pipeline config] [pipeline group]",
							Action: func(c *cli.Context) {
								gocdhost := c.Args()[0]
								path := c.Args()[1]
								group := c.Args().Get(2)

								gocd.Push(gocdhost, path, group)

							},
						},
						{
							Name:  "pull",
							Usage: "[gocd host] [path to pipeline config]",
							Action: func(c *cli.Context) {
								gocdhost := c.Args()[0]
								path := c.Args()[1]

								gocd.Pull(gocdhost, path)
							},
						},
						{
							Name:  "clone",
							Usage: "[gocd host] [path to pipeline config] [pipeline name]",
							Action: func(c *cli.Context) {
								gocdhost := c.Args()[0]
								path := c.Args()[1]
								name := c.Args()[2]

								gocd.Clone(gocdhost, path, name)
							},
						},
					},
				},
			},
		},
	}

	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package vcs

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

type vcsInfo struct {
	host     string
	pattern  string
	vcs      Type
	addCheck func(m map[string]string) (Type, error)
	regex    *regexp.Regexp
}

var vcsList = []*vcsInfo{
	{
		host:    "github.com",
		vcs:     Git,
		pattern: `^(github\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`,
	},
	{
		host:     "bitbucket.org",
		pattern:  `^(bitbucket\.org/(?P<name>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`,
		addCheck: checkBitbucket,
	},
	{
		host:    "launchpad.net",
		pattern: `^(launchpad\.net/(([A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)?|~[A-Za-z0-9_.\-]+/(\+junk|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`,
		vcs:     Bzr,
	},
	{
		host:    "git.launchpad.net",
		vcs:     Git,
		pattern: `^(git\.launchpad\.net/(([A-Za-z0-9_.\-]+)|~[A-Za-z0-9_.\-]+/(\+git|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))$`,
	},
	{
		host:    "go.googlesource.com",
		vcs:     Git,
		pattern: `^(go\.googlesource\.com/[A-Za-z0-9_.\-]+/?)$`,
	},
	// TODO: Once Google Code becomes fully deprecated this can be removed.
	{
		host:     "code.google.com",
		addCheck: checkGoogle,
		pattern:  `^(code\.google\.com/[pr]/(?P<project>[a-z0-9\-]+)(\.(?P<repo>[a-z0-9\-]+))?)(/[A-Za-z0-9_.\-]+)*$`,
	},
	// Alternative Google setup. This is the previous structure but it still works... until Google Code goes away.
	{
		addCheck: checkURL,
		pattern:  `^([a-z0-9_\-.]+)\.googlecode\.com/(?P<type>git|hg|svn)(/.*)?$`,
	},
	// If none of the previous detect the type they will fall to this looking for the type in a generic sense
	// by the extension to the path.
	{
		addCheck: checkURL,
		pattern:  `\.(?P<type>git|hg|svn|bzr)$`,
	},
}

func init() {
	// Precompile the regular expressions used to check VCS locations.
	for _, v := range vcsList {
		v.regex = regexp.MustCompile(v.pattern)
	}
}

// This function is really a hack around Go redirects rather than around
// something VCS related. Should this be moved to the glide project or a
// helper function?
func detectVcsFromRemote(vcsURL string) (Type, string, error) {
	t, e := detectVcsFromURL(vcsURL)
	if e == nil {
		return t, vcsURL, nil
	}

	// Need to test for vanity or paths like golang.org/x/

	// TODO: Test for 3xx redirect codes and handle appropriately.

	// Pages like https://golang.org/x/net provide an html document with
	// meta tags containing a location to work with. The go tool uses
	// a meta tag with the name go-import which is what we use here.
	// godoc.org also has one call go-source that we do not need to use.
	// The value of go-import is in the form "prefix vcs repo". The prefix
	// should match the vcsURL and the repo is a location that can be
	// checked out. Note, to get the html document you you need to add
	// ?go-get=1 to the url.
	u, err := url.Parse(vcsURL)
	if err != nil {
		return Type(""), "", err
	}
	if u.RawQuery == "" {
		u.RawQuery = "go-get=1"
	} else {
		u.RawQuery = u.RawQuery + "+go-get=1"
	}
	checkURL := u.String()
	resp, err := http.Get(checkURL)
	defer resp.Body.Close()
	if err != nil {
		return Type(""), "", ErrCannotDetectVCS
	}

	t, nu, err := parseImportFromBody(u, resp.Body)
	if err != nil {
		return Type(""), "", err
	} else if t == "" || nu == "" {
		return Type(""), "", ErrCannotDetectVCS
	}

	return t, nu, nil
}

// From a remote vcs url attempt to detect the VCS.
func detectVcsFromURL(vcsURL string) (Type, error) {
	u, err := url.Parse(vcsURL)
	if err != nil {
		return "", err
	}

	// If there is no host found we cannot detect the VCS from
	// the url. Note, URIs beginning with git@github using the ssh
	// syntax fail this check.
	if u.Host == "" {
		return "", ErrCannotDetectVCS
	}

	// Try to detect from known hosts, such as Github
	for _, v := range vcsList {
		if v.host != "" && v.host != u.Host {
			continue
		}

		// Make sure the pattern matches for an actual repo location. For example,
		// we should fail if the VCS listed is github.com/masterminds as that's
		// not actually a repo.
		uCheck := u.Host + u.Path
		m := v.regex.FindStringSubmatch(uCheck)
		if m == nil {
			if v.host != "" {
				return "", ErrCannotDetectVCS
			}

			continue
		}

		// If we are here the host matches. If the host has a singular
		// VCS type, such as Github, we can return the type right away.
		if v.vcs != "" {
			return v.vcs, nil
		}

		// Run additional checks to determine try and determine the repo
		// for the matched service.
		info := make(map[string]string)
		for i, name := range v.regex.SubexpNames() {
			if name != "" {
				info[name] = m[i]
			}
		}
		t, err := v.addCheck(info)
		if err != nil {
			return "", ErrCannotDetectVCS
		}

		return t, nil

	}

	// Unable to determine the vcs from the url.
	return "", ErrCannotDetectVCS
}

// Bitbucket provides an API for checking the VCS.
func checkBitbucket(i map[string]string) (Type, error) {

	// The part of the response we care about.
	var response struct {
		SCM Type `json:"scm"`
	}

	u := expand(i, "https://api.bitbucket.org/1.0/repositories/{name}")
	data, err := get(u)
	if err != nil {
		return "", err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return "", fmt.Errorf("Decoding error %s: %v", u, err)
	}

	return response.SCM, nil

}

// Google supports Git, Hg, and Svn. The SVN style is only
// supported through their legacy setup at <project>.googlecode.com.
// I wonder if anyone is actually using SVN support.
func checkGoogle(i map[string]string) (Type, error) {

	// To figure out which of the VCS types is used in Google Code you need
	// to parse a web page and find it. Ugh. I mean... ugh.
	var hack = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`)

	d, err := get(expand(i, "https://code.google.com/p/{project}/source/checkout?repo={repo}"))
	if err != nil {
		return "", err
	}

	if m := hack.FindSubmatch(d); m != nil {
		if vcs := string(m[1]); vcs != "" {
			if vcs == "svn" {
				// While Google supports SVN it can only be used with the legacy
				// urls of <project>.googlecode.com. I considered creating a new
				// error for this problem but Google Code is going away and there
				// is support for the legacy structure.
				return "", ErrCannotDetectVCS
			}

			return Type(vcs), nil
		}
	}

	return "", ErrCannotDetectVCS
}

// Expect a type key on i with the exact type detected from the regex.
func checkURL(i map[string]string) (Type, error) {
	return Type(i["type"]), nil
}

func get(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("%s: %s", url, resp.Status)
	}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("%s: %v", url, err)
	}
	return b, nil
}

func expand(match map[string]string, s string) string {
	for k, v := range match {
		s = strings.Replace(s, "{"+k+"}", v, -1)
	}
	return s
}

func parseImportFromBody(ur *url.URL, r io.ReadCloser) (tp Type, u string, err error) {
	d := xml.NewDecoder(r)
	d.CharsetReader = charsetReader
	d.Strict = false
	var t xml.Token
	for {
		t, err = d.Token()
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return
		}
		if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
			return
		}
		if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
			return
		}
		e, ok := t.(xml.StartElement)
		if !ok || !strings.EqualFold(e.Name.Local, "meta") {
			continue
		}
		if attrValue(e.Attr, "name") != "go-import" {
			continue
		}
		if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {

			// If this the second time a go-import statement has been detected
			// return an error. There should only be one import statement per
			// html file. We don't simply return the first found in order to
			// detect pages including more than one.
			// Should this be a different error?
			if tp != "" || u != "" {
				tp = Type("")
				u = ""
				err = ErrCannotDetectVCS
				return
			}

			// If the prefix supplied by the remote system isn't a prefix to the
			// url we're fetching return an error. This will work for exact
			// matches and prefixes. For example, golang.org/x/net as a prefix
			// will match for golang.org/x/net and golang.org/x/net/context.
			// Should this be a different error?
			vcsURL := ur.Host + ur.Path
			if !strings.HasPrefix(vcsURL, f[0]) {
				err = ErrCannotDetectVCS
				return
			}

			// We check to make sure the string in the html document is one of
			// the VCS we support. Do not want to blindly trust a string value
			// in an HTML doc.
			switch Type(f[1]) {
			case Git:
				tp = Git
			case Svn:
				tp = Svn
			case Bzr:
				tp = Bzr
			case Hg:
				tp = Hg
			}

			u = f[2]
		}
	}
}

func charsetReader(charset string, input io.Reader) (io.Reader, error) {
	switch strings.ToLower(charset) {
	case "ascii":
		return input, nil
	default:
		return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
	}
}

func attrValue(attrs []xml.Attr, name string) string {
	for _, a := range attrs {
		if strings.EqualFold(a.Name.Local, name) {
			return a.Value
		}
	}
	return ""
}
<commit_msg>Don't defer response body close til after error check.<commit_after>package vcs

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

type vcsInfo struct {
	host     string
	pattern  string
	vcs      Type
	addCheck func(m map[string]string) (Type, error)
	regex    *regexp.Regexp
}

var vcsList = []*vcsInfo{
	{
		host:    "github.com",
		vcs:     Git,
		pattern: `^(github\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`,
	},
	{
		host:     "bitbucket.org",
		pattern:  `^(bitbucket\.org/(?P<name>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`,
		addCheck: checkBitbucket,
	},
	{
		host:    "launchpad.net",
		pattern: `^(launchpad\.net/(([A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)?|~[A-Za-z0-9_.\-]+/(\+junk|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`,
		vcs:     Bzr,
	},
	{
		host:    "git.launchpad.net",
		vcs:     Git,
		pattern: `^(git\.launchpad\.net/(([A-Za-z0-9_.\-]+)|~[A-Za-z0-9_.\-]+/(\+git|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))$`,
	},
	{
		host:    "go.googlesource.com",
		vcs:     Git,
		pattern: `^(go\.googlesource\.com/[A-Za-z0-9_.\-]+/?)$`,
	},
	// TODO: Once Google Code becomes fully deprecated this can be removed.
	{
		host:     "code.google.com",
		addCheck: checkGoogle,
		pattern:  `^(code\.google\.com/[pr]/(?P<project>[a-z0-9\-]+)(\.(?P<repo>[a-z0-9\-]+))?)(/[A-Za-z0-9_.\-]+)*$`,
	},
	// Alternative Google setup. This is the previous structure but it still works... until Google Code goes away.
	{
		addCheck: checkURL,
		pattern:  `^([a-z0-9_\-.]+)\.googlecode\.com/(?P<type>git|hg|svn)(/.*)?$`,
	},
	// If none of the previous detect the type they will fall to this looking for the type in a generic sense
	// by the extension to the path.
	{
		addCheck: checkURL,
		pattern:  `\.(?P<type>git|hg|svn|bzr)$`,
	},
}

func init() {
	// Precompile the regular expressions used to check VCS locations.
	for _, v := range vcsList {
		v.regex = regexp.MustCompile(v.pattern)
	}
}

// This function is really a hack around Go redirects rather than around
// something VCS related. Should this be moved to the glide project or a
// helper function?
func detectVcsFromRemote(vcsURL string) (Type, string, error) {
	t, e := detectVcsFromURL(vcsURL)
	if e == nil {
		return t, vcsURL, nil
	}

	// Need to test for vanity or paths like golang.org/x/

	// TODO: Test for 3xx redirect codes and handle appropriately.

	// Pages like https://golang.org/x/net provide an html document with
	// meta tags containing a location to work with. The go tool uses
	// a meta tag with the name go-import which is what we use here.
	// godoc.org also has one call go-source that we do not need to use.
	// The value of go-import is in the form "prefix vcs repo". The prefix
	// should match the vcsURL and the repo is a location that can be
	// checked out. Note, to get the html document you you need to add
	// ?go-get=1 to the url.
	u, err := url.Parse(vcsURL)
	if err != nil {
		return Type(""), "", err
	}
	if u.RawQuery == "" {
		u.RawQuery = "go-get=1"
	} else {
		u.RawQuery = u.RawQuery + "+go-get=1"
	}
	checkURL := u.String()
	resp, err := http.Get(checkURL)
	if err != nil {
		return Type(""), "", ErrCannotDetectVCS
	}
	defer resp.Body.Close()

	t, nu, err := parseImportFromBody(u, resp.Body)
	if err != nil {
		return Type(""), "", err
	} else if t == "" || nu == "" {
		return Type(""), "", ErrCannotDetectVCS
	}

	return t, nu, nil
}

// From a remote vcs url attempt to detect the VCS.
func detectVcsFromURL(vcsURL string) (Type, error) {
	u, err := url.Parse(vcsURL)
	if err != nil {
		return "", err
	}

	// If there is no host found we cannot detect the VCS from
	// the url. Note, URIs beginning with git@github using the ssh
	// syntax fail this check.
	if u.Host == "" {
		return "", ErrCannotDetectVCS
	}

	// Try to detect from known hosts, such as Github
	for _, v := range vcsList {
		if v.host != "" && v.host != u.Host {
			continue
		}

		// Make sure the pattern matches for an actual repo location. For example,
		// we should fail if the VCS listed is github.com/masterminds as that's
		// not actually a repo.
		uCheck := u.Host + u.Path
		m := v.regex.FindStringSubmatch(uCheck)
		if m == nil {
			if v.host != "" {
				return "", ErrCannotDetectVCS
			}

			continue
		}

		// If we are here the host matches. If the host has a singular
		// VCS type, such as Github, we can return the type right away.
		if v.vcs != "" {
			return v.vcs, nil
		}

		// Run additional checks to determine try and determine the repo
		// for the matched service.
		info := make(map[string]string)
		for i, name := range v.regex.SubexpNames() {
			if name != "" {
				info[name] = m[i]
			}
		}
		t, err := v.addCheck(info)
		if err != nil {
			return "", ErrCannotDetectVCS
		}

		return t, nil

	}

	// Unable to determine the vcs from the url.
	return "", ErrCannotDetectVCS
}

// Bitbucket provides an API for checking the VCS.
func checkBitbucket(i map[string]string) (Type, error) {

	// The part of the response we care about.
	var response struct {
		SCM Type `json:"scm"`
	}

	u := expand(i, "https://api.bitbucket.org/1.0/repositories/{name}")
	data, err := get(u)
	if err != nil {
		return "", err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return "", fmt.Errorf("Decoding error %s: %v", u, err)
	}

	return response.SCM, nil

}

// Google supports Git, Hg, and Svn. The SVN style is only
// supported through their legacy setup at <project>.googlecode.com.
// I wonder if anyone is actually using SVN support.
func checkGoogle(i map[string]string) (Type, error) {

	// To figure out which of the VCS types is used in Google Code you need
	// to parse a web page and find it. Ugh. I mean... ugh.
	var hack = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`)

	d, err := get(expand(i, "https://code.google.com/p/{project}/source/checkout?repo={repo}"))
	if err != nil {
		return "", err
	}

	if m := hack.FindSubmatch(d); m != nil {
		if vcs := string(m[1]); vcs != "" {
			if vcs == "svn" {
				// While Google supports SVN it can only be used with the legacy
				// urls of <project>.googlecode.com. I considered creating a new
				// error for this problem but Google Code is going away and there
				// is support for the legacy structure.
				return "", ErrCannotDetectVCS
			}

			return Type(vcs), nil
		}
	}

	return "", ErrCannotDetectVCS
}

// Expect a type key on i with the exact type detected from the regex.
func checkURL(i map[string]string) (Type, error) {
	return Type(i["type"]), nil
}

func get(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("%s: %s", url, resp.Status)
	}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("%s: %v", url, err)
	}
	return b, nil
}

func expand(match map[string]string, s string) string {
	for k, v := range match {
		s = strings.Replace(s, "{"+k+"}", v, -1)
	}
	return s
}

func parseImportFromBody(ur *url.URL, r io.ReadCloser) (tp Type, u string, err error) {
	d := xml.NewDecoder(r)
	d.CharsetReader = charsetReader
	d.Strict = false
	var t xml.Token
	for {
		t, err = d.Token()
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return
		}
		if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
			return
		}
		if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
			return
		}
		e, ok := t.(xml.StartElement)
		if !ok || !strings.EqualFold(e.Name.Local, "meta") {
			continue
		}
		if attrValue(e.Attr, "name") != "go-import" {
			continue
		}
		if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {

			// If this the second time a go-import statement has been detected
			// return an error. There should only be one import statement per
			// html file. We don't simply return the first found in order to
			// detect pages including more than one.
			// Should this be a different error?
			if tp != "" || u != "" {
				tp = Type("")
				u = ""
				err = ErrCannotDetectVCS
				return
			}

			// If the prefix supplied by the remote system isn't a prefix to the
			// url we're fetching return an error. This will work for exact
			// matches and prefixes. For example, golang.org/x/net as a prefix
			// will match for golang.org/x/net and golang.org/x/net/context.
			// Should this be a different error?
			vcsURL := ur.Host + ur.Path
			if !strings.HasPrefix(vcsURL, f[0]) {
				err = ErrCannotDetectVCS
				return
			}

			// We check to make sure the string in the html document is one of
			// the VCS we support. Do not want to blindly trust a string value
			// in an HTML doc.
			switch Type(f[1]) {
			case Git:
				tp = Git
			case Svn:
				tp = Svn
			case Bzr:
				tp = Bzr
			case Hg:
				tp = Hg
			}

			u = f[2]
		}
	}
}

func charsetReader(charset string, input io.Reader) (io.Reader, error) {
	switch strings.ToLower(charset) {
	case "ascii":
		return input, nil
	default:
		return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
	}
}

func attrValue(attrs []xml.Attr, name string) string {
	for _, a := range attrs {
		if strings.EqualFold(a.Name.Local, name) {
			return a.Value
		}
	}
	return ""
}
<|endoftext|>


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

/*
Package router 0.4.0 provides fast HTTP request router.

The router matches incoming requests by the request method and the path.
If a handle is registered for this path and method, the router delegates the
request to defined handler.
The router package is useful to prepare a RESTful API for Go services.
It has JSON output, which bind automatically for relevant type of data.
The router has timer feature to display duration of request handling in the header

Simplest example (serve static route):

	package main

	import (
		"github.com/takama/router"
	)

	func Hello(c *router.Control) {
		c.Body("Hello")
	}

	func main() {
		r := router.New()
		r.GET("/hello", Hello)

		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Check it:

	curl -i http://localhost:8888/hello

Serve dynamic route with parameter:

	func main() {
		r := router.New()
		r.GET("/hello/:name", func(c *router.Control) {
			c.Code(200).Body("Hello " + c.Get(":name"))
		})

		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Checks JSON Content-Type automatically:

	// Data is helper to construct JSON
	type Data map[string]interface{}

	func main() {
		r := router.New()
		r.GET("/settings/database/:db", func(c *router.Control) {
			data := map[string]map[string]string{
				"Database settings": {
					"database": c.Get(":db"),
					"host":     "localhost",
					"port":     "3306",
				},
			}
			c.Code(200).Body(data)
		})
		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Custom handler with "Access-Control-Allow" options and compact JSON:

	// Data is helper to construct JSON
	type Data map[string]interface{}

	func baseHandler(handle router.Handle) router.Handle {
		return func(c *router.Control) {
			c.CompactJSON(true)
			if origin := c.Request.Header.Get("Origin"); origin != "" {
				c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
				c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
			}
			handle(c)
		}
	}

	func Info(c *router.Control) {
		data := Data{
			"debug": true,
			"error": false,
		}
		c.Body(data)
	}

	func main() {
		r := router.New()
		r.CustomHandler = baseHandler
		r.GET("/info", Info)

		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Use google json style `https://google.github.io/styleguide/jsoncstyleguide.xml`

	func main() {
		r := router.New()
		r.GET("/api/v1/people/:action/:id", func(c *router.Control) {

			// Do something

			c.Method("people." + c.Get(":action"))
			c.SetParams(map[string]string{"userId": c.Get(":id")})
			c.SetError(http.StatusNotFound, "UserId not found")
			c.AddError(router.Error{Message: "Group or User not found"})
			c.Code(http.StatusNotFound).Body(nil)
		})
		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Go Router
*/
package router

import (
	"log"
	"net/http"
	"strings"
)

// Router represents a multiplexer for HTTP requests.
type Router struct {
	// List of handlers which accociated with known http methods (GET, POST ...)
	handlers map[string]*parser

	// NotFound is called when unknown HTTP method or a handler not found.
	// If it is not set, http.NotFound is used.
	// Please overwrite this if need your own NotFound handler.
	NotFound Handle

	// PanicHandler is called when panic happen.
	// The handler prevents your server from crashing and should be used to return
	// http status code http.StatusInternalServerError (500)
	PanicHandler Handle

	// CustomHandler is called allways if defined
	CustomHandler func(Handle) Handle

	// Logger activates logging user function for each requests
	Logger Handle
}

// Handle type is aliased to type of handler function.
type Handle func(*Control)

// New it returns a new multiplexer (Router).
func New() *Router {
	return &Router{handlers: make(map[string]*parser)}
}

// GET is a shortcut for Router Handle("GET", path, handle)
func (r *Router) GET(path string, h Handle) {
	r.Handle("GET", path, h)
}

// POST is a shortcut for Router Handle("POST", path, handle)
func (r *Router) POST(path string, h Handle) {
	r.Handle("POST", path, h)
}

// PUT is a shortcut for Router Handle("PUT", path, handle)
func (r *Router) PUT(path string, h Handle) {
	r.Handle("PUT", path, h)
}

// DELETE is a shortcut for Router Handle("DELETE", path, handle)
func (r *Router) DELETE(path string, h Handle) {
	r.Handle("DELETE", path, h)
}

// HEAD is a shortcut for Router Handle("HEAD", path, handle)
func (r *Router) HEAD(path string, h Handle) {
	r.Handle("HEAD", path, h)
}

// OPTIONS is a shortcut for Router Handle("OPTIONS", path, handle)
func (r *Router) OPTIONS(path string, h Handle) {
	r.Handle("OPTIONS", path, h)
}

// Handle registers a new request handle with the given path and method.
func (r *Router) Handle(method, path string, h Handle) {
	if r.handlers[method] == nil {
		r.handlers[method] = newParser()
	}
	r.handlers[method].register(path, h)
}

// Handler allows the usage of an http.Handler as a request handle.
func (r *Router) Handler(method, path string, handler http.Handler) {
	r.Handle(method, path,
		func(c *Control) {
			handler.ServeHTTP(c.Writer, c.Request)
		},
	)
}

// HandlerFunc allows the usage of an http.HandlerFunc as a request handle.
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
	r.Handle(method, path,
		func(c *Control) {
			handler(c.Writer, c.Request)
		},
	)
}

// Lookup returns handler and URL parameters that associated with path.
func (r *Router) Lookup(method, path string) (Handle, []Param, bool) {
	if parser := r.handlers[method]; parser != nil {
		return parser.get(path)
	}
	return nil, nil, false
}

// AllowedMethods returns list of allowed methods
func (r *Router) AllowedMethods(path string) []string {
	var allowed []string
	for method, parser := range r.handlers {
		if _, _, ok := parser.get(path); ok {
			allowed = append(allowed, method)
		}
	}

	return allowed
}

// Listen and serve on requested host and port.
func (r *Router) Listen(hostPort string) {
	if err := http.ListenAndServe(hostPort, r); err != nil {
		log.Fatal(err)
	}
}

// ServeHTTP implements http.Handler interface.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	defer func() {
		if recovery := recover(); recovery != nil {
			if r.PanicHandler != nil {
				c := &Control{Request: req, Writer: w}
				r.PanicHandler(c)
			} else {
				log.Println("Recovered in handler:", req.Method, req.URL.Path)
			}
		}
	}()
	if r.Logger != nil {
		c := &Control{Request: req, Writer: w}
		r.Logger(c)
	}
	if _, ok := r.handlers[req.Method]; ok {
		if handle, params, ok := r.handlers[req.Method].get(req.URL.Path); ok {
			c := &Control{Request: req, Writer: w}
			if len(params) > 0 {
				c.params = append(c.params, params...)
			}
			if r.CustomHandler != nil {
				r.CustomHandler(handle)(c)
			} else {
				handle(c)
			}
			return
		}
	}
	allowed := r.AllowedMethods(req.URL.Path)

	if len(allowed) == 0 {
		if r.NotFound != nil {
			c := &Control{Request: req, Writer: w}
			r.NotFound(c)
		} else {
			http.NotFound(w, req)
		}
		return
	}

	w.Header().Add("Allow", strings.Join(allowed, ", "))
	http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
<commit_msg>Bumped version number to 0.4.1<commit_after>// Copyright 2015 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package router 0.4.1 provides fast HTTP request router.

The router matches incoming requests by the request method and the path.
If a handle is registered for this path and method, the router delegates the
request to defined handler.
The router package is useful to prepare a RESTful API for Go services.
It has JSON output, which bind automatically for relevant type of data.
The router has timer feature to display duration of request handling in the header

Simplest example (serve static route):

	package main

	import (
		"github.com/takama/router"
	)

	func Hello(c *router.Control) {
		c.Body("Hello")
	}

	func main() {
		r := router.New()
		r.GET("/hello", Hello)

		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Check it:

	curl -i http://localhost:8888/hello

Serve dynamic route with parameter:

	func main() {
		r := router.New()
		r.GET("/hello/:name", func(c *router.Control) {
			c.Code(200).Body("Hello " + c.Get(":name"))
		})

		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Checks JSON Content-Type automatically:

	// Data is helper to construct JSON
	type Data map[string]interface{}

	func main() {
		r := router.New()
		r.GET("/settings/database/:db", func(c *router.Control) {
			data := map[string]map[string]string{
				"Database settings": {
					"database": c.Get(":db"),
					"host":     "localhost",
					"port":     "3306",
				},
			}
			c.Code(200).Body(data)
		})
		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Custom handler with "Access-Control-Allow" options and compact JSON:

	// Data is helper to construct JSON
	type Data map[string]interface{}

	func baseHandler(handle router.Handle) router.Handle {
		return func(c *router.Control) {
			c.CompactJSON(true)
			if origin := c.Request.Header.Get("Origin"); origin != "" {
				c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
				c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
			}
			handle(c)
		}
	}

	func Info(c *router.Control) {
		data := Data{
			"debug": true,
			"error": false,
		}
		c.Body(data)
	}

	func main() {
		r := router.New()
		r.CustomHandler = baseHandler
		r.GET("/info", Info)

		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Use google json style `https://google.github.io/styleguide/jsoncstyleguide.xml`

	func main() {
		r := router.New()
		r.GET("/api/v1/people/:action/:id", func(c *router.Control) {

			// Do something

			c.Method("people." + c.Get(":action"))
			c.SetParams(map[string]string{"userId": c.Get(":id")})
			c.SetError(http.StatusNotFound, "UserId not found")
			c.AddError(router.Error{Message: "Group or User not found"})
			c.Code(http.StatusNotFound).Body(nil)
		})
		// Listen and serve on 0.0.0.0:8888
		r.Listen(":8888")
	}

Go Router
*/
package router

import (
	"log"
	"net/http"
	"strings"
)

// Router represents a multiplexer for HTTP requests.
type Router struct {
	// List of handlers which accociated with known http methods (GET, POST ...)
	handlers map[string]*parser

	// NotFound is called when unknown HTTP method or a handler not found.
	// If it is not set, http.NotFound is used.
	// Please overwrite this if need your own NotFound handler.
	NotFound Handle

	// PanicHandler is called when panic happen.
	// The handler prevents your server from crashing and should be used to return
	// http status code http.StatusInternalServerError (500)
	PanicHandler Handle

	// CustomHandler is called allways if defined
	CustomHandler func(Handle) Handle

	// Logger activates logging user function for each requests
	Logger Handle
}

// Handle type is aliased to type of handler function.
type Handle func(*Control)

// New it returns a new multiplexer (Router).
func New() *Router {
	return &Router{handlers: make(map[string]*parser)}
}

// GET is a shortcut for Router Handle("GET", path, handle)
func (r *Router) GET(path string, h Handle) {
	r.Handle("GET", path, h)
}

// POST is a shortcut for Router Handle("POST", path, handle)
func (r *Router) POST(path string, h Handle) {
	r.Handle("POST", path, h)
}

// PUT is a shortcut for Router Handle("PUT", path, handle)
func (r *Router) PUT(path string, h Handle) {
	r.Handle("PUT", path, h)
}

// DELETE is a shortcut for Router Handle("DELETE", path, handle)
func (r *Router) DELETE(path string, h Handle) {
	r.Handle("DELETE", path, h)
}

// HEAD is a shortcut for Router Handle("HEAD", path, handle)
func (r *Router) HEAD(path string, h Handle) {
	r.Handle("HEAD", path, h)
}

// OPTIONS is a shortcut for Router Handle("OPTIONS", path, handle)
func (r *Router) OPTIONS(path string, h Handle) {
	r.Handle("OPTIONS", path, h)
}

// Handle registers a new request handle with the given path and method.
func (r *Router) Handle(method, path string, h Handle) {
	if r.handlers[method] == nil {
		r.handlers[method] = newParser()
	}
	r.handlers[method].register(path, h)
}

// Handler allows the usage of an http.Handler as a request handle.
func (r *Router) Handler(method, path string, handler http.Handler) {
	r.Handle(method, path,
		func(c *Control) {
			handler.ServeHTTP(c.Writer, c.Request)
		},
	)
}

// HandlerFunc allows the usage of an http.HandlerFunc as a request handle.
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
	r.Handle(method, path,
		func(c *Control) {
			handler(c.Writer, c.Request)
		},
	)
}

// Lookup returns handler and URL parameters that associated with path.
func (r *Router) Lookup(method, path string) (Handle, []Param, bool) {
	if parser := r.handlers[method]; parser != nil {
		return parser.get(path)
	}
	return nil, nil, false
}

// AllowedMethods returns list of allowed methods
func (r *Router) AllowedMethods(path string) []string {
	var allowed []string
	for method, parser := range r.handlers {
		if _, _, ok := parser.get(path); ok {
			allowed = append(allowed, method)
		}
	}

	return allowed
}

// Listen and serve on requested host and port.
func (r *Router) Listen(hostPort string) {
	if err := http.ListenAndServe(hostPort, r); err != nil {
		log.Fatal(err)
	}
}

// ServeHTTP implements http.Handler interface.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	defer func() {
		if recovery := recover(); recovery != nil {
			if r.PanicHandler != nil {
				c := &Control{Request: req, Writer: w}
				r.PanicHandler(c)
			} else {
				log.Println("Recovered in handler:", req.Method, req.URL.Path)
			}
		}
	}()
	if r.Logger != nil {
		c := &Control{Request: req, Writer: w}
		r.Logger(c)
	}
	if _, ok := r.handlers[req.Method]; ok {
		if handle, params, ok := r.handlers[req.Method].get(req.URL.Path); ok {
			c := &Control{Request: req, Writer: w}
			if len(params) > 0 {
				c.params = append(c.params, params...)
			}
			if r.CustomHandler != nil {
				r.CustomHandler(handle)(c)
			} else {
				handle(c)
			}
			return
		}
	}
	allowed := r.AllowedMethods(req.URL.Path)

	if len(allowed) == 0 {
		if r.NotFound != nil {
			c := &Control{Request: req, Writer: w}
			r.NotFound(c)
		} else {
			http.NotFound(w, req)
		}
		return
	}

	w.Header().Add("Allow", strings.Join(allowed, ", "))
	http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
<|endoftext|>


<commit_before>// Package router provides a router linking uris to handlers taking a context
package router

import (
	"fmt"
	"net/http"
	"os"
	"path"
	"strings"
	"time"
)

// ContextHandler is our standard handler function, accepting a router.Context interface
type ContextHandler func(Context)

// Logger Interface for a simple logger (the stdlib log pkg and the fragmenta log pkg conform)
type Logger interface {
	Printf(format string, args ...interface{})
}

// Config Interface to retreive configuration details of the server
type Config interface {
	Production() bool
	Config(string) string
}

// Router stores and handles the routes
type Router struct {

	// File handler
	FileHandler ContextHandler

	// The logger passed to actions within the context on each request
	Logger Logger

	// The server config passed to actions within the context on each request
	Config Config

	// A list of routes
	routes []*Route

	// A list of pre-action filters, applied before any handler
	filters []ContextHandler
}

// New creates a new router
func New(l Logger, s Config) (*Router, error) {
	r := &Router{
		FileHandler: fileHandler,
		Logger:      l,
		Config:      s,
	}

	// Set our router to handle all routes
	http.Handle("/", r)
	return r, nil
}

// Logf logs this message and the given arguments
func (r *Router) Logf(format string, v ...interface{}) {
	r.Logger.Printf(format, v...)
}

// Log this format and arguments
func (r *Router) Log(message string) {
	r.Logf(message)
}

// Add a new route
func (r *Router) Add(pattern string, handler ContextHandler) *Route {
	// Create a new route
	route, err := NewRoute(pattern, handler)
	if err != nil {
		r.Logf("#error Creating regexp failed for route %s:%s", pattern, err)
	}

	// Store this route in the router
	r.routes = append(r.routes, route)

	// Return route for chaining
	return route
}

// AddRedirect adds a new redirect this is just a route with a redirect path set
func (r *Router) AddRedirect(pattern string, redirectPath string, status int) *Route {

	// Create a new route for redirecting - NB no handler or auth handler
	route, err := NewRoute(pattern, nil)
	if err != nil {
		r.Logf("#error Creating redirect failed for route %s:%s", pattern, err)
	}
	route.RedirectPath = redirectPath
	route.RedirectStatus = status

	// Store this route in the router
	r.routes = append(r.routes, route)

	// Return route for chaining
	return route
}

// AddFilter adds a new filter
func (r *Router) AddFilter(filter ContextHandler) {
	// Store this filter in the router list
	r.filters = append(r.filters, filter)

}

// Reset stored state in routes (parsed params)
func (r *Router) Reset() {
	for _, r := range r.routes {
		r.Reset()
	}
}

// ServeHTTP - Central dispatcher for web requests - sets up the context and hands off to handlers
func (r *Router) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	// Reset any cached state at the end of each request
	defer r.Reset()

	// Started GET "/" for 127.0.0.1 at 2014-07-01 14:15:32 +0100
	started := time.Now()
	summary := fmt.Sprintf("%s %s for %s", request.Method, request.URL.Path, request.RemoteAddr)

	// Clean the path
	canonicalPath := path.Clean(request.URL.Path)
	if len(canonicalPath) == 0 {
		canonicalPath = "/"
	} else if canonicalPath[0] != '/' {
		canonicalPath = "/" + canonicalPath
	}

	status := 200

	// Log starting the request - we should have some way of excluding logs
	// not hard-coded like this FIXME
	logging := !strings.HasPrefix(canonicalPath, "/assets") && !strings.HasPrefix(canonicalPath, "/files")
	if logging {
		r.Logf("#info Started %s", summary)
	}

	// Set up a handler to handle request if not redirected
	var handler ContextHandler

	var route *Route

	// Try finding a route
	route = r.findRoute(canonicalPath, request)

	if route != nil {

		if logging {
			r.Logf("#info Handling with route %s", route)
		}

		if route.Handler != nil {
			handler = route.Handler
		} else if route.RedirectStatus != 0 {
			// Redirect to RedirectPath and return
			http.Redirect(writer, request, route.RedirectPath, route.RedirectStatus)
			return
		}
	}

	// Setup the context
	context := &ConcreteContext{
		writer:  writer,
		request: request,
		path:    canonicalPath,
		route:   route,
		logger:  r.Logger,
		config:  r.Config,
		data:    make(map[string]interface{}, 0),
	}

	// Call any filters
	for _, f := range r.filters {
		f(context)
	}

	// If handler is not nil, serve, else fall back to defaults
	if handler != nil {

		// Handle the request
		handler(context)

		// Log the end of handling
		end := time.Since(started).String()

		if logging {
			r.Logf("#info Finished %s status %d in %s", summary, status, end)
		}

	} else {
		// If no route or handler, try default file handler to serve static files
		r.FileHandler(context)
	}

}

// This may return nil
// Canonical path should have been cleaned first
func (r *Router) findRoute(canonicalPath string, request *http.Request) *Route {
	for _, r := range r.routes {
		// Check method (GET/PUT), then check path
		if r.MatchMethod(request.Method) && r.MatchPath(canonicalPath) {
			return r
		}
	}
	return nil
}

// Default static file handler - this is the last line of handlers
func fileHandler(context Context) {

	// Assuming we're running from the root of the website
	localPath := "./public" + path.Clean(context.Path())

	if _, err := os.Stat(localPath); err != nil {
		if os.IsNotExist(err) {
			// Where it doesn't exist render not found
			http.Redirect(context, context.Request(), context.Path(), http.StatusNotFound)
			return
		}

		// For other errors return not authorised
		http.Redirect(context, context.Request(), context.Path(), http.StatusUnauthorized)
		return
	}

	// If the file exists and we can access it, serve it
	http.ServeFile(context, context.Request(), localPath)
}

// Redirect uses status 302 StatusFound by default - this is not a permanent redirect
// We don't accept external or relative paths for security reasons
func Redirect(context Context, path string) {
	// 301 - http.StatusMovedPermanently - permanent redirect
	// 302 - http.StatusFound - tmp redirect
	RedirectStatus(context, path, http.StatusFound)
}

// RedirectStatus redirects setting the status code (for example unauthorized)
// We don't accept external or relative paths for security reasons
func RedirectStatus(context Context, path string, status int) {

	// We check this is an internal path - to redirect externally use http.Redirect directly
	if strings.HasPrefix(path, "/") && !strings.Contains(path, ":") {
		// Status may be any value, e.g.
		// 301 - http.StatusMovedPermanently - permanent redirect
		// 302 - http.StatusFound - tmp redirect
		// 401 - Access denied
		context.Logf("#info Redirecting (%d) to path:%s", status, path)
		http.Redirect(context, context.Request(), path, status)
		return
	}

	context.Logf("#error Ignoring redirect to external path %s", path)
}

// RedirectExternal redirects setting the status code (for example unauthorized), but does no checks on the path
// Use with caution.
func RedirectExternal(context Context, path string) {
	http.Redirect(context, context.Request(), path, http.StatusFound)

}
<commit_msg>Added mutex to router<commit_after>// Package router provides a router linking uris to handlers taking a context
package router

import (
	"fmt"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
	"time"
)

// ContextHandler is our standard handler function, accepting a router.Context interface
type ContextHandler func(Context)

// Logger Interface for a simple logger (the stdlib log pkg and the fragmenta log pkg conform)
type Logger interface {
	Printf(format string, args ...interface{})
}

// Config Interface to retreive configuration details of the server
type Config interface {
	Production() bool
	Config(string) string
}

// Router stores and handles the routes
type Router struct {
	// Mutex protects routes and filters
	mu sync.RWMutex

	// File handler
	FileHandler ContextHandler

	// The logger passed to actions within the context on each request
	Logger Logger

	// The server config passed to actions within the context on each request
	Config Config

	// A list of routes
	routes []*Route

	// A list of pre-action filters, applied before any handler
	filters []ContextHandler
}

// New creates a new router
func New(l Logger, s Config) (*Router, error) {
	r := &Router{
		FileHandler: fileHandler,
		Logger:      l,
		Config:      s,
	}

	// Set our router to handle all routes
	http.Handle("/", r)
	return r, nil
}

// Logf logs this message and the given arguments
func (r *Router) Logf(format string, v ...interface{}) {
	r.Logger.Printf(format, v...)
}

// Log this format and arguments
func (r *Router) Log(message string) {
	r.Logf(message)
}

// Add a new route
func (r *Router) Add(pattern string, handler ContextHandler) *Route {
	r.mu.Lock()
	defer r.mu.Unlock()

	// Create a new route
	route, err := NewRoute(pattern, handler)
	if err != nil {
		r.Logf("#error Creating regexp failed for route %s:%s", pattern, err)
	}

	// Store this route in the router
	r.routes = append(r.routes, route)

	// Return route for chaining
	return route
}

// AddRedirect adds a new redirect this is just a route with a redirect path set
func (r *Router) AddRedirect(pattern string, redirectPath string, status int) *Route {
	r.mu.Lock()
	defer r.mu.Unlock()

	// Create a new route for redirecting - NB no handler or auth handler
	route, err := NewRoute(pattern, nil)
	if err != nil {
		r.Logf("#error Creating redirect failed for route %s:%s", pattern, err)
	}
	route.RedirectPath = redirectPath
	route.RedirectStatus = status

	// Store this route in the router
	r.routes = append(r.routes, route)

	// Return route for chaining
	return route
}

// AddFilter adds a new filter
func (r *Router) AddFilter(filter ContextHandler) {
	r.mu.Lock()
	defer r.mu.Unlock()
	// Store this filter in the router list
	r.filters = append(r.filters, filter)

}

// Reset stored state in routes (parsed params)
func (r *Router) Reset() {
	for _, r := range r.routes {
		r.Reset()
	}
}

// ServeHTTP - Central dispatcher for web requests - sets up the context and hands off to handlers
func (r *Router) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	// Reset any cached state at the end of each request
	defer r.Reset()

	// Started GET "/" for 127.0.0.1 at 2014-07-01 14:15:32 +0100
	started := time.Now()
	summary := fmt.Sprintf("%s %s for %s", request.Method, request.URL.Path, request.RemoteAddr)

	// Clean the path
	canonicalPath := path.Clean(request.URL.Path)
	if len(canonicalPath) == 0 {
		canonicalPath = "/"
	} else if canonicalPath[0] != '/' {
		canonicalPath = "/" + canonicalPath
	}

	status := 200

	// Log starting the request - we should have some way of excluding logs
	// not hard-coded like this FIXME
	logging := !strings.HasPrefix(canonicalPath, "/assets") && !strings.HasPrefix(canonicalPath, "/files")
	if logging {
		r.Logf("#info Started %s", summary)
	}

	// Set up a handler to handle request if not redirected
	var handler ContextHandler

	var route *Route

	// Try finding a route
	route = r.findRoute(canonicalPath, request)

	if route != nil {

		if logging {
			r.Logf("#info Handling with route %s", route)
		}

		if route.Handler != nil {
			handler = route.Handler
		} else if route.RedirectStatus != 0 {
			// Redirect to RedirectPath and return
			http.Redirect(writer, request, route.RedirectPath, route.RedirectStatus)
			return
		}
	}

	// Setup the context
	context := &ConcreteContext{
		writer:  writer,
		request: request,
		path:    canonicalPath,
		route:   route,
		logger:  r.Logger,
		config:  r.Config,
		data:    make(map[string]interface{}, 0),
	}

	// Call any filters
	r.runFilters(context)

	// If handler is not nil, serve, else fall back to defaults
	if handler != nil {

		// Handle the request
		handler(context)

		// Log the end of handling
		end := time.Since(started).String()

		if logging {
			r.Logf("#info Finished %s status %d in %s", summary, status, end)
		}

	} else {
		// If no route or handler, try default file handler to serve static files
		r.FileHandler(context)
	}

}

func (r *Router) runFilters(context Context) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	for _, f := range r.filters {
		f(context)
	}
}

// This may return nil
// Canonical path should have been cleaned first
func (r *Router) findRoute(canonicalPath string, request *http.Request) *Route {
	r.mu.RLock()
	defer r.mu.RUnlock()

	for _, r := range r.routes {
		// Check method (GET/PUT), then check path
		if r.MatchMethod(request.Method) && r.MatchPath(canonicalPath) {
			return r
		}
	}
	return nil
}

// Default static file handler - this is the last line of handlers
func fileHandler(context Context) {

	// Assuming we're running from the root of the website
	localPath := "./public" + path.Clean(context.Path())

	if _, err := os.Stat(localPath); err != nil {
		if os.IsNotExist(err) {
			// Where it doesn't exist render not found
			http.Redirect(context, context.Request(), context.Path(), http.StatusNotFound)
			return
		}

		// For other errors return not authorised
		http.Redirect(context, context.Request(), context.Path(), http.StatusUnauthorized)
		return
	}

	// If the file exists and we can access it, serve it
	http.ServeFile(context, context.Request(), localPath)
}

// Redirect uses status 302 StatusFound by default - this is not a permanent redirect
// We don't accept external or relative paths for security reasons
func Redirect(context Context, path string) {
	// 301 - http.StatusMovedPermanently - permanent redirect
	// 302 - http.StatusFound - tmp redirect
	RedirectStatus(context, path, http.StatusFound)
}

// RedirectStatus redirects setting the status code (for example unauthorized)
// We don't accept external or relative paths for security reasons
func RedirectStatus(context Context, path string, status int) {

	// We check this is an internal path - to redirect externally use http.Redirect directly
	if strings.HasPrefix(path, "/") && !strings.Contains(path, ":") {
		// Status may be any value, e.g.
		// 301 - http.StatusMovedPermanently - permanent redirect
		// 302 - http.StatusFound - tmp redirect
		// 401 - Access denied
		context.Logf("#info Redirecting (%d) to path:%s", status, path)
		http.Redirect(context, context.Request(), path, status)
		return
	}

	context.Logf("#error Ignoring redirect to external path %s", path)
}

// RedirectExternal redirects setting the status code (for example unauthorized), but does no checks on the path
// Use with caution.
func RedirectExternal(context Context, path string) {
	http.Redirect(context, context.Request(), path, http.StatusFound)

}
<|endoftext|>


<commit_before>// Package denco provides fast URL router.
package denco

import (
	"fmt"
	"sort"
	"strings"
)

const (
	// A special character for path parameter.
	ParamCharacter = ':'

	// A special character for wildcard path parameter.
	WildcardCharacter = '*'

	// TerminalCharacter is a special character for end of path.
	TerminationCharacter = '#'

	// Block size of array of BASE/CHECK of Double-Array.
	blockSize = 256
)

// Router represents a URL router.
type Router struct {
	static map[string]interface{}
	param  *doubleArray
}

// New returns a new Router.
func New() *Router {
	return &Router{
		static: make(map[string]interface{}),
		param:  newDoubleArray(blockSize),
	}
}

// Lookup returns data and path parameters that associated with path.
// params is a slice of the Param that arranged in the order in which parameters appeared.
// e.g. when built routing path is "/path/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
func (rt *Router) Lookup(path string) (data interface{}, params []Param, found bool) {
	if data, found := rt.static[path]; found {
		return data, nil, true
	}
	idx, values, found := rt.param.lookup(path, nil, 1)
	if !found {
		return nil, nil, false
	}
	nd := rt.param.node[-rt.param.bc[idx].base]
	if nd == nil {
		return nil, nil, false
	}
	if len(values) > 0 {
		params = make([]Param, len(values))
		for i, v := range values {
			params[i] = Param{Name: nd.paramNames[i], Value: v}
		}
	}
	return nd.data, params, true
}

// Build builds URL router from records.
func (rt *Router) Build(records []Record) error {
	statics, params := makeRecords(records)
	for _, r := range statics {
		rt.static[r.Key] = r.Value
	}
	if err := rt.param.build(params, 1, 0, make(map[int]struct{})); err != nil {
		return err
	}
	return nil
}

// Param represents name and value of path parameter.
type Param struct {
	Name  string
	Value string
}

type doubleArray struct {
	bc   []baseCheck
	node []*node
}

func newDoubleArray(size int) *doubleArray {
	return &doubleArray{
		bc:   newBaseCheckArray(size + 1),
		node: []*node{nil}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
	}
}

// newBaseCheckArray returns a new slice of baseCheck with given size.
func newBaseCheckArray(size int) []baseCheck {
	return make([]baseCheck, size)
}

// baseCheck represents a BASE/CHECK node.
type baseCheck struct {
	base      int
	check     byte
	paramType paramType
}

type paramType uint8

func (p paramType) IsParam() bool {
	return p&paramTypeSingle == paramTypeSingle
}

func (p paramType) IsWildcard() bool {
	return p&paramTypeWildcard == paramTypeWildcard
}

func (p paramType) IsAny() bool {
	return p > 0
}

func (p *paramType) SetSingle() {
	*p |= paramTypeSingle
}

func (p *paramType) SetWildcard() {
	*p |= paramTypeWildcard
}

const (
	paramTypeSingle = 1 << iota
	paramTypeWildcard
)

func (da *doubleArray) lookup(path string, params []string, idx int) (int, []string, bool) {
	indices := make([]uint64, 0, 1)
	for i := 0; i < len(path); i++ {
		if da.bc[idx].paramType.IsAny() {
			indices = append(indices, (uint64(i&0xffffffff)<<32)|uint64(idx&0xffffffff))
		}
		c := path[i]
		next := nextIndex(da.bc[idx].base, c)
		if da.bc[next].check != c {
			goto BACKTRACKING
		}
		idx = next
	}
	if next := nextIndex(da.bc[idx].base, TerminationCharacter); da.bc[next].check == TerminationCharacter {
		return next, params, true
	}
	return -1, nil, false
BACKTRACKING:
	for j := len(indices) - 1; j >= 0; j-- {
		i, idx := int((indices[j]>>32)&0xffffffff), int(indices[j]&0xffffffff)
		if da.bc[idx].paramType.IsParam() {
			next := NextSeparator(path, i)
			idx := nextIndex(da.bc[idx].base, ParamCharacter)
			params := append(params, path[i:next])
			path := path[next:]
			if idx, params, found := da.lookup(path, params, idx); found {
				return idx, params, true
			}
		}
		if da.bc[idx].paramType.IsWildcard() {
			idx := nextIndex(da.bc[idx].base, WildcardCharacter)
			params := append(params, path[i:])
			return idx, params, true
		}
	}
	return -1, nil, false
}

// build builds double-array from records.
func (da *doubleArray) build(srcs []*record, idx, depth int, usedBase map[int]struct{}) error {
	sort.Sort(recordSlice(srcs))
	base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
	if err != nil {
		return err
	}
	if leaf != nil {
		nd, err := makeNode(leaf)
		if err != nil {
			return err
		}
		da.bc[idx].base = -len(da.node)
		da.node = append(da.node, nd)
	}
	for _, sib := range siblings {
		da.setCheck(nextIndex(base, sib.c), sib.c)
	}
	for _, sib := range siblings {
		records := srcs[sib.start:sib.end]
		switch sib.c {
		case ParamCharacter:
			for _, r := range records {
				next := NextSeparator(r.Key, depth+1)
				name := r.Key[depth+1 : next]
				r.paramNames = append(r.paramNames, name)
				r.Key = r.Key[next:]
			}
			da.bc[idx].paramType.SetSingle()
			if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
				return err
			}
		case WildcardCharacter:
			r := records[0]
			name := r.Key[depth+1 : len(r.Key)-1]
			r.paramNames = append(r.paramNames, name)
			r.Key = ""
			da.bc[idx].paramType.SetWildcard()
			if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
				return err
			}
		default:
			if err := da.build(records, nextIndex(base, sib.c), depth+1, usedBase); err != nil {
				return err
			}
		}
	}
	return nil
}

// setBase sets BASE.
func (da *doubleArray) setBase(i, base int) {
	da.bc[i].base = base
}

// setCheck sets CHECK.
func (da *doubleArray) setCheck(i int, check byte) {
	da.bc[i].check = check
}

// extendBaseCheckArray extends array of BASE/CHECK.
func (da *doubleArray) extendBaseCheckArray() {
	da.bc = append(da.bc, newBaseCheckArray(blockSize)...)
}

// findEmptyIndex returns an index of unused BASE/CHECK node.
func (da *doubleArray) findEmptyIndex(start int) int {
	i := start
	for ; i < len(da.bc); i++ {
		if da.bc[i].base == 0 && da.bc[i].check == 0 {
			break
		}
	}
	return i
}

// findBase returns good BASE.
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
	for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
		base = nextIndex(idx, firstChar)
		if _, used := usedBase[base]; used {
			continue
		}
		i := 0
		for ; i < len(siblings); i++ {
			next := nextIndex(base, siblings[i].c)
			if len(da.bc) <= next {
				da.extendBaseCheckArray()
			}
			if len(da.bc) <= next || da.bc[next].base != 0 || da.bc[next].check != 0 {
				break
			}
		}
		if i == len(siblings) {
			break
		}
	}
	usedBase[base] = struct{}{}
	return base
}

func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
	siblings, leaf, err = makeSiblings(records, depth)
	if err != nil {
		return -1, nil, nil, err
	}
	if len(siblings) < 1 {
		return -1, nil, leaf, nil
	}
	base = da.findBase(siblings, idx, usedBase)
	da.setBase(idx, base)
	return base, siblings, leaf, err
}

// node represents a node of Double-Array.
type node struct {
	data interface{}

	// Names of path parameters.
	paramNames []string
}

// makeNode returns a new node from record.
func makeNode(r *record) (*node, error) {
	dups := make(map[string]bool)
	for _, name := range r.paramNames {
		if dups[name] {
			return nil, fmt.Errorf("denco: path parameter `%v' is duplicated in the key `%v'", name, r.Key)
		}
		dups[name] = true
	}
	return &node{data: r.Value, paramNames: r.paramNames}, nil
}

// sibling represents an intermediate data of build for Double-Array.
type sibling struct {
	// An index of start of duplicated characters.
	start int

	// An index of end of duplicated characters.
	end int

	// A character of sibling.
	c byte
}

// nextIndex returns a next index of array of BASE/CHECK.
func nextIndex(base int, c byte) int {
	return base ^ int(c)
}

// makeSiblings returns slice of sibling.
func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, err error) {
	var (
		pc byte
		n  int
	)
	for i, r := range records {
		if len(r.Key) <= depth {
			leaf = r
			continue
		}
		c := r.Key[depth]
		switch {
		case pc < c:
			sib = append(sib, sibling{start: i, c: c})
		case pc == c:
			continue
		default:
			return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
		}
		if n > 0 {
			sib[n-1].end = i
		}
		pc = c
		n++
	}
	if n == 0 {
		return nil, leaf, nil
	}
	sib[n-1].end = len(records)
	return sib, leaf, nil
}

// Record represents a record data for router construction.
type Record struct {
	// Key for router construction.
	Key string

	// Result value for Key.
	Value interface{}
}

// NewRecord returns a new Record.
func NewRecord(key string, value interface{}) Record {
	return Record{
		Key:   key,
		Value: value,
	}
}

// record represents a record that use to build the Double-Array.
type record struct {
	Record
	paramNames []string
}

// makeRecords returns the records that use to build Double-Arrays.
func makeRecords(srcs []Record) (statics, params []*record) {
	spChars := string([]byte{ParamCharacter, WildcardCharacter})
	termChar := string(TerminationCharacter)
	for _, r := range srcs {
		if strings.ContainsAny(r.Key, spChars) {
			r.Key += termChar
			params = append(params, &record{Record: r})
		} else {
			statics = append(statics, &record{Record: r})
		}
	}
	return statics, params
}

// recordSlice represents a slice of Record for sort and implements the sort.Interface.
type recordSlice []*record

// Len implements the sort.Interface.Len.
func (rs recordSlice) Len() int {
	return len(rs)
}

// Less implements the sort.Interface.Less.
func (rs recordSlice) Less(i, j int) bool {
	return rs[i].Key < rs[j].Key
}

// Swap implements the sort.Interface.Swap.
func (rs recordSlice) Swap(i, j int) {
	rs[i], rs[j] = rs[j], rs[i]
}
<commit_msg>Optimize for memory usage<commit_after>// Package denco provides fast URL router.
package denco

import (
	"fmt"
	"sort"
	"strings"
)

const (
	// A special character for path parameter.
	ParamCharacter = ':'

	// A special character for wildcard path parameter.
	WildcardCharacter = '*'

	// TerminalCharacter is a special character for end of path.
	TerminationCharacter = '#'

	// Block size of array of BASE/CHECK of Double-Array.
	blockSize = 256
)

// Router represents a URL router.
type Router struct {
	static map[string]interface{}
	param  *doubleArray
}

// New returns a new Router.
func New() *Router {
	return &Router{
		static: make(map[string]interface{}),
		param:  newDoubleArray(blockSize),
	}
}

// Lookup returns data and path parameters that associated with path.
// params is a slice of the Param that arranged in the order in which parameters appeared.
// e.g. when built routing path is "/path/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
func (rt *Router) Lookup(path string) (data interface{}, params []Param, found bool) {
	if data, found := rt.static[path]; found {
		return data, nil, true
	}
	idx, values, found := rt.param.lookup(path, nil, 1)
	if !found {
		return nil, nil, false
	}
	nd := rt.param.node[rt.param.bc[idx].Index()]
	if nd == nil {
		return nil, nil, false
	}
	if len(values) > 0 {
		params = make([]Param, len(values))
		for i, v := range values {
			params[i] = Param{Name: nd.paramNames[i], Value: v}
		}
	}
	return nd.data, params, true
}

// Build builds URL router from records.
func (rt *Router) Build(records []Record) error {
	statics, params := makeRecords(records)
	for _, r := range statics {
		rt.static[r.Key] = r.Value
	}
	if err := rt.param.build(params, 1, 0, make(map[int]struct{})); err != nil {
		return err
	}
	return nil
}

// Param represents name and value of path parameter.
type Param struct {
	Name  string
	Value string
}

type doubleArray struct {
	bc   []baseCheck
	node []*node
}

func newDoubleArray(size int) *doubleArray {
	return &doubleArray{
		bc:   newBaseCheckArray(size + 1),
		node: []*node{nil}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
	}
}

// newBaseCheckArray returns a new slice of baseCheck with given size.
func newBaseCheckArray(size int) []baseCheck {
	return make([]baseCheck, size)
}

// baseCheck contains BASE, CHECK and Extra flags.
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK.
//
//  BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
// |----------------------|--|--------|
// 32                    10  8         0
type baseCheck uint32

func (bc baseCheck) Base() int {
	return int(bc >> 10)
}

func (bc *baseCheck) SetBase(base int) {
	*bc |= baseCheck(base) << 10
}

func (bc baseCheck) Check() byte {
	return byte(bc & 0xff)
}

func (bc *baseCheck) SetCheck(check byte) {
	*bc |= baseCheck(check)
}

func (bc baseCheck) IsEmpty() bool {
	return bc&0xfffffcff == 0
}

func (bc baseCheck) Index() int {
	return int(bc >> 10)
}

func (bc *baseCheck) SetIndex(i int) {
	*bc |= baseCheck(i) << 10
}

func (bc baseCheck) IsSingleParam() bool {
	return (bc>>8)&0x01 == 1
}

func (bc baseCheck) IsWildcardParam() bool {
	return (bc>>9)&0x01 == 1
}

func (bc baseCheck) IsAnyParam() bool {
	return (bc>>8)&3 != 0
}

func (bc *baseCheck) SetSingleParam() {
	*bc |= (1 << 8)
}

func (bc *baseCheck) SetWildcardParam() {
	*bc |= (1 << 9)
}

func (da *doubleArray) lookup(path string, params []string, idx int) (int, []string, bool) {
	indices := make([]uint64, 0, 1)
	for i := 0; i < len(path); i++ {
		if da.bc[idx].IsAnyParam() {
			indices = append(indices, (uint64(i&0xffffffff)<<32)|uint64(idx&0xffffffff))
		}
		c := path[i]
		next := nextIndex(da.bc[idx].Base(), c)
		if da.bc[next].Check() != c {
			goto BACKTRACKING
		}
		idx = next
	}
	if next := nextIndex(da.bc[idx].Base(), TerminationCharacter); da.bc[next].Check() == TerminationCharacter {
		return next, params, true
	}
	return -1, nil, false
BACKTRACKING:
	for j := len(indices) - 1; j >= 0; j-- {
		i, idx := int((indices[j]>>32)&0xffffffff), int(indices[j]&0xffffffff)
		if da.bc[idx].IsSingleParam() {
			next := NextSeparator(path, i)
			idx := nextIndex(da.bc[idx].Base(), ParamCharacter)
			params := append(params, path[i:next])
			path := path[next:]
			if idx, params, found := da.lookup(path, params, idx); found {
				return idx, params, true
			}
		}
		if da.bc[idx].IsWildcardParam() {
			idx := nextIndex(da.bc[idx].Base(), WildcardCharacter)
			params := append(params, path[i:])
			return idx, params, true
		}
	}
	return -1, nil, false
}

// build builds double-array from records.
func (da *doubleArray) build(srcs []*record, idx, depth int, usedBase map[int]struct{}) error {
	sort.Sort(recordSlice(srcs))
	base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
	if err != nil {
		return err
	}
	if leaf != nil {
		nd, err := makeNode(leaf)
		if err != nil {
			return err
		}
		da.bc[idx].SetIndex(len(da.node))
		da.node = append(da.node, nd)
	}
	for _, sib := range siblings {
		da.setCheck(nextIndex(base, sib.c), sib.c)
	}
	for _, sib := range siblings {
		records := srcs[sib.start:sib.end]
		switch sib.c {
		case ParamCharacter:
			for _, r := range records {
				next := NextSeparator(r.Key, depth+1)
				name := r.Key[depth+1 : next]
				r.paramNames = append(r.paramNames, name)
				r.Key = r.Key[next:]
			}
			da.bc[idx].SetSingleParam()
			if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
				return err
			}
		case WildcardCharacter:
			r := records[0]
			name := r.Key[depth+1 : len(r.Key)-1]
			r.paramNames = append(r.paramNames, name)
			r.Key = ""
			da.bc[idx].SetWildcardParam()
			if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
				return err
			}
		default:
			if err := da.build(records, nextIndex(base, sib.c), depth+1, usedBase); err != nil {
				return err
			}
		}
	}
	return nil
}

// setBase sets BASE.
func (da *doubleArray) setBase(i, base int) {
	da.bc[i].SetBase(base)
}

// setCheck sets CHECK.
func (da *doubleArray) setCheck(i int, check byte) {
	da.bc[i].SetCheck(check)
}

// extendBaseCheckArray extends array of BASE/CHECK.
func (da *doubleArray) extendBaseCheckArray() {
	da.bc = append(da.bc, newBaseCheckArray(blockSize)...)
}

// findEmptyIndex returns an index of unused BASE/CHECK node.
func (da *doubleArray) findEmptyIndex(start int) int {
	i := start
	for ; i < len(da.bc); i++ {
		if da.bc[i].IsEmpty() {
			break
		}
	}
	return i
}

// findBase returns good BASE.
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
	for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
		base = nextIndex(idx, firstChar)
		if _, used := usedBase[base]; used {
			continue
		}
		i := 0
		for ; i < len(siblings); i++ {
			next := nextIndex(base, siblings[i].c)
			if len(da.bc) <= next {
				da.extendBaseCheckArray()
			}
			if len(da.bc) <= next || !da.bc[next].IsEmpty() {
				break
			}
		}
		if i == len(siblings) {
			break
		}
	}
	usedBase[base] = struct{}{}
	return base
}

func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
	siblings, leaf, err = makeSiblings(records, depth)
	if err != nil {
		return -1, nil, nil, err
	}
	if len(siblings) < 1 {
		return -1, nil, leaf, nil
	}
	base = da.findBase(siblings, idx, usedBase)
	da.setBase(idx, base)
	return base, siblings, leaf, err
}

// node represents a node of Double-Array.
type node struct {
	data interface{}

	// Names of path parameters.
	paramNames []string
}

// makeNode returns a new node from record.
func makeNode(r *record) (*node, error) {
	dups := make(map[string]bool)
	for _, name := range r.paramNames {
		if dups[name] {
			return nil, fmt.Errorf("denco: path parameter `%v' is duplicated in the key `%v'", name, r.Key)
		}
		dups[name] = true
	}
	return &node{data: r.Value, paramNames: r.paramNames}, nil
}

// sibling represents an intermediate data of build for Double-Array.
type sibling struct {
	// An index of start of duplicated characters.
	start int

	// An index of end of duplicated characters.
	end int

	// A character of sibling.
	c byte
}

// nextIndex returns a next index of array of BASE/CHECK.
func nextIndex(base int, c byte) int {
	return base ^ int(c)
}

// makeSiblings returns slice of sibling.
func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, err error) {
	var (
		pc byte
		n  int
	)
	for i, r := range records {
		if len(r.Key) <= depth {
			leaf = r
			continue
		}
		c := r.Key[depth]
		switch {
		case pc < c:
			sib = append(sib, sibling{start: i, c: c})
		case pc == c:
			continue
		default:
			return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
		}
		if n > 0 {
			sib[n-1].end = i
		}
		pc = c
		n++
	}
	if n == 0 {
		return nil, leaf, nil
	}
	sib[n-1].end = len(records)
	return sib, leaf, nil
}

// Record represents a record data for router construction.
type Record struct {
	// Key for router construction.
	Key string

	// Result value for Key.
	Value interface{}
}

// NewRecord returns a new Record.
func NewRecord(key string, value interface{}) Record {
	return Record{
		Key:   key,
		Value: value,
	}
}

// record represents a record that use to build the Double-Array.
type record struct {
	Record
	paramNames []string
}

// makeRecords returns the records that use to build Double-Arrays.
func makeRecords(srcs []Record) (statics, params []*record) {
	spChars := string([]byte{ParamCharacter, WildcardCharacter})
	termChar := string(TerminationCharacter)
	for _, r := range srcs {
		if strings.ContainsAny(r.Key, spChars) {
			r.Key += termChar
			params = append(params, &record{Record: r})
		} else {
			statics = append(statics, &record{Record: r})
		}
	}
	return statics, params
}

// recordSlice represents a slice of Record for sort and implements the sort.Interface.
type recordSlice []*record

// Len implements the sort.Interface.Len.
func (rs recordSlice) Len() int {
	return len(rs)
}

// Less implements the sort.Interface.Less.
func (rs recordSlice) Less(i, j int) bool {
	return rs[i].Key < rs[j].Key
}

// Swap implements the sort.Interface.Swap.
func (rs recordSlice) Swap(i, j int) {
	rs[i], rs[j] = rs[j], rs[i]
}
<|endoftext|>