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

package shareable

import (
	"fmt"
	"image"
	"image/color"
	"runtime"
	"sync"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/graphicsutil"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/packing"
	"github.com/hajimehoshi/ebiten/internal/restorable"
)

type backend struct {
	restorable *restorable.Image

	// If page is nil, the backend is not shared.
	page *packing.Page
}

func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) {
	// If the region is allocated without any extention, it's fine.
	if n := b.page.Alloc(width, height); n != nil {
		return n, true
	}

	// Simulate the extending the page and calculate the appropriate page size.
	page := b.page.Clone()
	nExtended := 0
	for {
		if !page.Extend() {
			// The page can't be extended any more. Return as failure.
			return nil, false
		}
		nExtended++
		if n := page.Alloc(width, height); n != nil {
			break
		}
	}

	for i := 0; i < nExtended; i++ {
		b.page.Extend()
	}
	s := b.page.Size()
	newImg := restorable.NewImage(s, s, false)
	oldImg := b.restorable
	w, h := oldImg.Size()
	vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, nil)
	is := graphicsutil.QuadIndices()
	newImg.DrawImage(oldImg, vs, is, opengl.CompositeModeCopy, graphics.FilterNearest)
	oldImg.Dispose()
	b.restorable = newImg

	n := b.page.Alloc(width, height)
	if n == nil {
		panic("not reached")
	}
	return n, true
}

var (
	// backendsM is a mutex for critical sections of the backend and packing.Node objects.
	backendsM sync.Mutex

	// theBackends is a set of actually shared images.
	theBackends = []*backend{}
)

type Image struct {
	width    int
	height   int
	disposed bool

	backend *backend

	node          *packing.Node
	countForShare int

	neverShared bool
}

func (i *Image) moveTo(dst *Image) {
	dst.dispose(false)
	*dst = *i

	// i is no longer available but Dispose must not be called
	// since i and dst have the same values like node.
	runtime.SetFinalizer(i, nil)
}

func (i *Image) isShared() bool {
	return i.node != nil
}

func (i *Image) IsSharedForTesting() bool {
	backendsM.Lock()
	defer backendsM.Unlock()
	return i.isShared()
}

func (i *Image) ensureNotShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if !i.isShared() {
		return
	}

	x, y, w, h := i.region()
	newImg := restorable.NewImage(w, h, false)
	vw, vh := i.backend.restorable.Size()
	vs := graphicsutil.QuadVertices(vw, vh, x, y, x+w, y+h, 1, 0, 0, 1, 0, 0, nil)
	is := graphicsutil.QuadIndices()
	newImg.DrawImage(i.backend.restorable, vs, is, opengl.CompositeModeCopy, graphics.FilterNearest)

	i.dispose(false)
	i.backend = &backend{
		restorable: newImg,
	}
}

func (i *Image) forceShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if i.isShared() {
		return
	}

	if !i.shareable() {
		panic("not reached")
	}

	newI := NewImage(i.width, i.height)
	pixels := make([]byte, 4*i.width*i.height)
	for y := 0; y < i.height; y++ {
		for x := 0; x < i.width; x++ {
			c := i.at(x, y)
			pixels[4*(x+i.width*y)] = c.R
			pixels[4*(x+i.width*y)+1] = c.G
			pixels[4*(x+i.width*y)+2] = c.B
			pixels[4*(x+i.width*y)+3] = c.A
		}
	}
	newI.replacePixels(pixels)
	newI.moveTo(i)
	i.countForShare = 0
}

func (i *Image) region() (x, y, width, height int) {
	if i.backend == nil {
		panic("not reached")
	}
	if !i.isShared() {
		w, h := i.backend.restorable.Size()
		return 0, 0, w, h
	}
	return i.node.Region()
}

func (i *Image) Size() (width, height int) {
	return i.width, i.height
}

func (i *Image) QuadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32, colorm *affine.ColorM) []float32 {
	if i.backend == nil {
		i.allocate(true)
	}
	dx, dy, _, _ := i.region()
	w, h := i.backend.restorable.SizePowerOf2()
	return graphicsutil.QuadVertices(w, h, sx0+dx, sy0+dy, sx1+dx, sy1+dy, a, b, c, d, tx, ty, colorm)
}

const MaxCountForShare = 10

func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, mode opengl.CompositeMode, filter graphics.Filter) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if img.disposed {
		panic("shareable: the drawing source image must not be disposed")
	}
	if i.disposed {
		panic("shareable: the drawing target image must not be disposed")
	}
	if img.backend == nil {
		img.allocate(true)
	}

	i.ensureNotShared()

	// Compare i and img after ensuring i is not shared, or
	// i and img might share the same texture even though i != img.
	if i.backend.restorable == img.backend.restorable {
		panic("shareable: Image.DrawImage: img must be different from the receiver")
	}

	i.backend.restorable.DrawImage(img.backend.restorable, vertices, indices, mode, filter)

	i.countForShare = 0
	if !img.isShared() && img.shareable() {
		img.countForShare++
		if img.countForShare >= MaxCountForShare {
			img.forceShared()
			img.countForShare = 0
		}
	}
}

func (i *Image) ReplacePixels(p []byte) {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.replacePixels(p)
}

func (i *Image) replacePixels(p []byte) {
	if i.disposed {
		panic("shareable: the image must not be disposed")
	}
	if i.backend == nil {
		i.allocate(true)
	}

	x, y, w, h := i.region()
	if l := 4 * w * h; len(p) != l {
		panic(fmt.Sprintf("shareable: len(p) was %d but must be %d", len(p), l))
	}
	i.backend.restorable.ReplacePixels(p, x, y, w, h)
}

func (i *Image) At(x, y int) color.RGBA {
	backendsM.Lock()
	defer backendsM.Unlock()
	return i.at(x, y)
}

func (i *Image) at(x, y int) color.RGBA {
	if i.backend == nil {
		return color.RGBA{}
	}

	ox, oy, w, h := i.region()
	if x < 0 || y < 0 || x >= w || y >= h {
		return color.RGBA{}
	}

	return i.backend.restorable.At(x+ox, y+oy)
}

func (i *Image) Dispose() {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.dispose(true)
}

func (i *Image) dispose(markDisposed bool) {
	defer func() {
		if markDisposed {
			i.disposed = true
		}
		i.backend = nil
		i.node = nil
		if markDisposed {
			runtime.SetFinalizer(i, nil)
		}
	}()

	if i.disposed {
		return
	}

	if i.backend == nil {
		// Not allocated yet.
		return
	}

	if !i.isShared() {
		i.backend.restorable.Dispose()
		return
	}

	i.backend.page.Free(i.node)
	if !i.backend.page.IsEmpty() {
		// As this part can be reused, this should be cleared explicitly.
		x0, y0, x1, y1 := i.region()
		i.backend.restorable.ReplacePixels(nil, x0, y0, x1, y1)
		return
	}

	i.backend.restorable.Dispose()
	index := -1
	for idx, sh := range theBackends {
		if sh == i.backend {
			index = idx
			break
		}
	}
	if index == -1 {
		panic("not reached")
	}
	theBackends = append(theBackends[:index], theBackends[index+1:]...)
}

func (i *Image) IsInvalidated() (bool, error) {
	backendsM.Lock()
	defer backendsM.Unlock()
	v, err := i.backend.restorable.IsInvalidated()
	return v, err
}

func NewImage(width, height int) *Image {
	// Actual allocation is done lazily.
	return &Image{
		width:  width,
		height: height,
	}
}

func (i *Image) shareable() bool {
	if i.neverShared {
		return false
	}
	return i.width <= maxSize && i.height <= maxSize
}

func (i *Image) allocate(shareable bool) {
	if i.backend != nil {
		panic("not reached")
	}

	if !shareable || !i.shareable() {
		i.backend = &backend{
			restorable: restorable.NewImage(i.width, i.height, false),
		}
		runtime.SetFinalizer(i, (*Image).Dispose)
		return
	}

	for _, b := range theBackends {
		if n, ok := b.TryAlloc(i.width, i.height); ok {
			i.backend = b
			i.node = n
			runtime.SetFinalizer(i, (*Image).Dispose)
			return
		}
	}
	size := initSize
	for i.width > size || i.height > size {
		if size == maxSize {
			panic("not reached")
		}
		size *= 2
	}

	b := &backend{
		restorable: restorable.NewImage(size, size, false),
		page:       packing.NewPage(size, maxSize),
	}
	theBackends = append(theBackends, b)

	n := b.page.Alloc(i.width, i.height)
	if n == nil {
		panic("not reached")
	}
	i.backend = b
	i.node = n
	runtime.SetFinalizer(i, (*Image).Dispose)
	return
}

func NewVolatileImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewImage(width, height, true)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
		neverShared: true,
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func NewScreenFramebufferImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewScreenFramebufferImage(width, height)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
		neverShared: true,
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func InitializeGLState() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.InitializeGLState()
}

func ResolveStaleImages() {
	backendsM.Lock()
	defer backendsM.Unlock()
	restorable.ResolveStaleImages()
}

func IsRestoringEnabled() bool {
	// As IsRestoringEnabled is an immutable state, no need to lock here.
	return restorable.IsRestoringEnabled()
}

func Restore() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Restore()
}

func Images() []image.Image {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Images()
}

func Error() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Error()
}
<commit_msg>shareable: Bug fix: forceShared should allocate backend as shareable<commit_after>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package shareable

import (
	"fmt"
	"image"
	"image/color"
	"runtime"
	"sync"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/graphicsutil"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/packing"
	"github.com/hajimehoshi/ebiten/internal/restorable"
)

type backend struct {
	restorable *restorable.Image

	// If page is nil, the backend is not shared.
	page *packing.Page
}

func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) {
	// If the region is allocated without any extention, it's fine.
	if n := b.page.Alloc(width, height); n != nil {
		return n, true
	}

	// Simulate the extending the page and calculate the appropriate page size.
	page := b.page.Clone()
	nExtended := 0
	for {
		if !page.Extend() {
			// The page can't be extended any more. Return as failure.
			return nil, false
		}
		nExtended++
		if n := page.Alloc(width, height); n != nil {
			break
		}
	}

	for i := 0; i < nExtended; i++ {
		b.page.Extend()
	}
	s := b.page.Size()
	newImg := restorable.NewImage(s, s, false)
	oldImg := b.restorable
	w, h := oldImg.Size()
	vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, nil)
	is := graphicsutil.QuadIndices()
	newImg.DrawImage(oldImg, vs, is, opengl.CompositeModeCopy, graphics.FilterNearest)
	oldImg.Dispose()
	b.restorable = newImg

	n := b.page.Alloc(width, height)
	if n == nil {
		panic("not reached")
	}
	return n, true
}

var (
	// backendsM is a mutex for critical sections of the backend and packing.Node objects.
	backendsM sync.Mutex

	// theBackends is a set of actually shared images.
	theBackends = []*backend{}
)

type Image struct {
	width    int
	height   int
	disposed bool

	backend *backend

	node          *packing.Node
	countForShare int

	neverShared bool
}

func (i *Image) moveTo(dst *Image) {
	dst.dispose(false)
	*dst = *i

	// i is no longer available but Dispose must not be called
	// since i and dst have the same values like node.
	runtime.SetFinalizer(i, nil)
}

func (i *Image) isShared() bool {
	return i.node != nil
}

func (i *Image) IsSharedForTesting() bool {
	backendsM.Lock()
	defer backendsM.Unlock()
	return i.isShared()
}

func (i *Image) ensureNotShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if !i.isShared() {
		return
	}

	x, y, w, h := i.region()
	newImg := restorable.NewImage(w, h, false)
	vw, vh := i.backend.restorable.Size()
	vs := graphicsutil.QuadVertices(vw, vh, x, y, x+w, y+h, 1, 0, 0, 1, 0, 0, nil)
	is := graphicsutil.QuadIndices()
	newImg.DrawImage(i.backend.restorable, vs, is, opengl.CompositeModeCopy, graphics.FilterNearest)

	i.dispose(false)
	i.backend = &backend{
		restorable: newImg,
	}
}

func (i *Image) forceShared() {
	if i.backend == nil {
		i.allocate(true)
		return
	}

	if i.isShared() {
		return
	}

	if !i.shareable() {
		panic("not reached")
	}

	newI := NewImage(i.width, i.height)
	pixels := make([]byte, 4*i.width*i.height)
	for y := 0; y < i.height; y++ {
		for x := 0; x < i.width; x++ {
			c := i.at(x, y)
			pixels[4*(x+i.width*y)] = c.R
			pixels[4*(x+i.width*y)+1] = c.G
			pixels[4*(x+i.width*y)+2] = c.B
			pixels[4*(x+i.width*y)+3] = c.A
		}
	}
	newI.replacePixels(pixels)
	newI.moveTo(i)
	i.countForShare = 0
}

func (i *Image) region() (x, y, width, height int) {
	if i.backend == nil {
		panic("not reached")
	}
	if !i.isShared() {
		w, h := i.backend.restorable.Size()
		return 0, 0, w, h
	}
	return i.node.Region()
}

func (i *Image) Size() (width, height int) {
	return i.width, i.height
}

func (i *Image) QuadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32, colorm *affine.ColorM) []float32 {
	if i.backend == nil {
		i.allocate(true)
	}
	dx, dy, _, _ := i.region()
	w, h := i.backend.restorable.SizePowerOf2()
	return graphicsutil.QuadVertices(w, h, sx0+dx, sy0+dy, sx1+dx, sy1+dy, a, b, c, d, tx, ty, colorm)
}

const MaxCountForShare = 10

func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, mode opengl.CompositeMode, filter graphics.Filter) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if img.disposed {
		panic("shareable: the drawing source image must not be disposed")
	}
	if i.disposed {
		panic("shareable: the drawing target image must not be disposed")
	}
	if img.backend == nil {
		img.allocate(true)
	}

	i.ensureNotShared()

	// Compare i and img after ensuring i is not shared, or
	// i and img might share the same texture even though i != img.
	if i.backend.restorable == img.backend.restorable {
		panic("shareable: Image.DrawImage: img must be different from the receiver")
	}

	i.backend.restorable.DrawImage(img.backend.restorable, vertices, indices, mode, filter)

	i.countForShare = 0
	if !img.isShared() && img.shareable() {
		img.countForShare++
		if img.countForShare >= MaxCountForShare {
			img.forceShared()
			img.countForShare = 0
		}
	}
}

func (i *Image) ReplacePixels(p []byte) {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.replacePixels(p)
}

func (i *Image) replacePixels(p []byte) {
	if i.disposed {
		panic("shareable: the image must not be disposed")
	}
	if i.backend == nil {
		i.allocate(true)
	}

	x, y, w, h := i.region()
	if l := 4 * w * h; len(p) != l {
		panic(fmt.Sprintf("shareable: len(p) was %d but must be %d", len(p), l))
	}
	i.backend.restorable.ReplacePixels(p, x, y, w, h)
}

func (i *Image) At(x, y int) color.RGBA {
	backendsM.Lock()
	defer backendsM.Unlock()
	return i.at(x, y)
}

func (i *Image) at(x, y int) color.RGBA {
	if i.backend == nil {
		return color.RGBA{}
	}

	ox, oy, w, h := i.region()
	if x < 0 || y < 0 || x >= w || y >= h {
		return color.RGBA{}
	}

	return i.backend.restorable.At(x+ox, y+oy)
}

func (i *Image) Dispose() {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.dispose(true)
}

func (i *Image) dispose(markDisposed bool) {
	defer func() {
		if markDisposed {
			i.disposed = true
		}
		i.backend = nil
		i.node = nil
		if markDisposed {
			runtime.SetFinalizer(i, nil)
		}
	}()

	if i.disposed {
		return
	}

	if i.backend == nil {
		// Not allocated yet.
		return
	}

	if !i.isShared() {
		i.backend.restorable.Dispose()
		return
	}

	i.backend.page.Free(i.node)
	if !i.backend.page.IsEmpty() {
		// As this part can be reused, this should be cleared explicitly.
		x0, y0, x1, y1 := i.region()
		i.backend.restorable.ReplacePixels(nil, x0, y0, x1, y1)
		return
	}

	i.backend.restorable.Dispose()
	index := -1
	for idx, sh := range theBackends {
		if sh == i.backend {
			index = idx
			break
		}
	}
	if index == -1 {
		panic("not reached")
	}
	theBackends = append(theBackends[:index], theBackends[index+1:]...)
}

func (i *Image) IsInvalidated() (bool, error) {
	backendsM.Lock()
	defer backendsM.Unlock()
	v, err := i.backend.restorable.IsInvalidated()
	return v, err
}

func NewImage(width, height int) *Image {
	// Actual allocation is done lazily.
	return &Image{
		width:  width,
		height: height,
	}
}

func (i *Image) shareable() bool {
	if i.neverShared {
		return false
	}
	return i.width <= maxSize && i.height <= maxSize
}

func (i *Image) allocate(shareable bool) {
	if i.backend != nil {
		panic("not reached")
	}

	if !shareable || !i.shareable() {
		i.backend = &backend{
			restorable: restorable.NewImage(i.width, i.height, false),
		}
		runtime.SetFinalizer(i, (*Image).Dispose)
		return
	}

	for _, b := range theBackends {
		if n, ok := b.TryAlloc(i.width, i.height); ok {
			i.backend = b
			i.node = n
			runtime.SetFinalizer(i, (*Image).Dispose)
			return
		}
	}
	size := initSize
	for i.width > size || i.height > size {
		if size == maxSize {
			panic("not reached")
		}
		size *= 2
	}

	b := &backend{
		restorable: restorable.NewImage(size, size, false),
		page:       packing.NewPage(size, maxSize),
	}
	theBackends = append(theBackends, b)

	n := b.page.Alloc(i.width, i.height)
	if n == nil {
		panic("not reached")
	}
	i.backend = b
	i.node = n
	runtime.SetFinalizer(i, (*Image).Dispose)
	return
}

func NewVolatileImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewImage(width, height, true)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
		neverShared: true,
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func NewScreenFramebufferImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewScreenFramebufferImage(width, height)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
		neverShared: true,
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func InitializeGLState() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.InitializeGLState()
}

func ResolveStaleImages() {
	backendsM.Lock()
	defer backendsM.Unlock()
	restorable.ResolveStaleImages()
}

func IsRestoringEnabled() bool {
	// As IsRestoringEnabled is an immutable state, no need to lock here.
	return restorable.IsRestoringEnabled()
}

func Restore() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Restore()
}

func Images() []image.Image {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Images()
}

func Error() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Error()
}
<|endoftext|>


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

package workers

import (
	"fmt"
	"github.com/dchest/uniuri"
	"github.com/pquerna/hurl/common"
	"sync"
	"time"
)

type WorkerTask interface {
	Work(rv *common.Result) error
}

type Worker interface {
	Start(wg *sync.WaitGroup, reqChan chan int64, resChan chan *common.Result) error
	Halt() error
}

type newTask func(common.ConfigGetter) WorkerTask

var g_workers_tasks map[string]newTask

func init() {
	g_workers_tasks = make(map[string]newTask)
}

func Register(wt string, nt newTask) {
	g_workers_tasks[wt] = nt
}

type LocalWorker struct {
	WorkerType string
	wg         *sync.WaitGroup
	reqChan    chan int64
	resChan    chan *common.Result
	task       WorkerTask
}

func (lw *LocalWorker) runWorker(id string) {
	defer lw.wg.Done()

	for {
		reqNum, ok := <-lw.reqChan
		if !ok {
			return
		}
		rv := common.Result{Id: fmt.Sprintf("%s-%d", id, reqNum)}
		startTime := time.Now()
		err := lw.task.Work(&rv)
		duration := time.Since(startTime)
		if err != nil {
			// TODO: report this back to UI in a better way? Convert to ErrorResult?
			panic(err)
			return
		}
		rv.Duration = duration
		// TODO: results storage
		lw.resChan <- &rv
	}
}

func (lw *LocalWorker) Start(wg *sync.WaitGroup, reqChan chan int64, resChan chan *common.Result) error {
	lw.reqChan = reqChan
	lw.resChan = resChan
	lw.wg = wg
	lw.wg.Add(1)
	go lw.runWorker(uniuri.New())

	return nil
}

func (lw *LocalWorker) Halt() error {
	return nil
}

func Run(ui common.UI, task string, conf common.ConfigGetter) error {
	//	if clusterConf != "" {
	//		// TODO: add ClusterWorker
	//		return nil, fmt.Errorf("TODO: Cluster support")
	//	}
	wt, ok := g_workers_tasks[task]
	if !ok {
		return fmt.Errorf("unknown worker type: %s", task)
	}

	bconf := conf.GetBasicConfig()
	workers := make([]Worker, bconf.Concurrency)
	for index, _ := range workers {
		workers[index] = &LocalWorker{task: wt(conf)}
	}

	ui.WorkStart(bconf.NumRequests)
	defer func() {
		for _, worker := range workers {
			worker.Halt()
		}
	}()

	var wg sync.WaitGroup
	reqchan := make(chan int64, 1024*1024)
	// TODO: how big should this be?
	reschan := make(chan *common.Result)
	for _, worker := range workers {
		err := worker.Start(&wg, reqchan, reschan)
		if err != nil {
			return err
		}
	}

	var i int64

	for i = 0; i < bconf.NumRequests; i++ {
		reqchan <- i
		// TOOD: ui.WorkStatus(numDone int64)
	}
	go func() {
		i = 0
		for {
			_, ok := <-reschan
			if !ok {
				return
			}
			i++
			ui.WorkStatus(i)
		}
	}()

	close(reqchan)
	wg.Wait()
	close(reschan)
	ui.WorkEnd()

	return nil
}
<commit_msg>add another waitgroup, and break it out to a real func<commit_after>/**
 *  Copyright 2014 Paul Querna
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package workers

import (
	"fmt"
	"github.com/dchest/uniuri"
	"github.com/pquerna/hurl/common"
	"sync"
	"time"
)

type WorkerTask interface {
	Work(rv *common.Result) error
}

type Worker interface {
	Start(wg *sync.WaitGroup, reqChan chan int64, resChan chan *common.Result) error
	Halt() error
}

type newTask func(common.ConfigGetter) WorkerTask

var g_workers_tasks map[string]newTask

func init() {
	g_workers_tasks = make(map[string]newTask)
}

func Register(wt string, nt newTask) {
	g_workers_tasks[wt] = nt
}

type LocalWorker struct {
	WorkerType string
	wg         *sync.WaitGroup
	reqChan    chan int64
	resChan    chan *common.Result
	task       WorkerTask
}

func (lw *LocalWorker) runWorker(id string) {
	defer lw.wg.Done()

	for {
		reqNum, ok := <-lw.reqChan
		if !ok {
			return
		}
		rv := common.Result{Id: fmt.Sprintf("%s-%d", id, reqNum)}
		startTime := time.Now()
		err := lw.task.Work(&rv)
		duration := time.Since(startTime)
		if err != nil {
			// TODO: report this back to UI in a better way? Convert to ErrorResult?
			panic(err)
			return
		}
		rv.Duration = duration
		// TODO: results storage
		lw.resChan <- &rv
	}
}

func (lw *LocalWorker) Start(wg *sync.WaitGroup, reqChan chan int64, resChan chan *common.Result) error {
	lw.reqChan = reqChan
	lw.resChan = resChan
	lw.wg = wg
	lw.wg.Add(1)
	go lw.runWorker(uniuri.New())

	return nil
}

func (lw *LocalWorker) Halt() error {
	return nil
}

func resultHanlder(ui common.UI, wgres *sync.WaitGroup, resChan chan *common.Result) {
	defer func() { wgres.Done() }()
	var i int64 = 0
	for {
		_, ok := <-resChan
		if !ok {
			return
		}
		i++
		ui.WorkStatus(i)
	}
}

func Run(ui common.UI, task string, conf common.ConfigGetter) error {
	//	if clusterConf != "" {
	//		// TODO: add ClusterWorker
	//		return nil, fmt.Errorf("TODO: Cluster support")
	//	}
	wt, ok := g_workers_tasks[task]
	if !ok {
		return fmt.Errorf("unknown worker type: %s", task)
	}

	bconf := conf.GetBasicConfig()
	workers := make([]Worker, bconf.Concurrency)
	for index, _ := range workers {
		workers[index] = &LocalWorker{task: wt(conf)}
	}

	ui.WorkStart(bconf.NumRequests)
	defer func() {
		for _, worker := range workers {
			worker.Halt()
		}
	}()

	var wg sync.WaitGroup
	var wgres sync.WaitGroup
	reqchan := make(chan int64, 1024*1024)
	// TODO: how big should this be?
	reschan := make(chan *common.Result)
	for _, worker := range workers {
		err := worker.Start(&wg, reqchan, reschan)
		if err != nil {
			return err
		}
	}

	var i int64

	for i = 0; i < bconf.NumRequests; i++ {
		reqchan <- i
		// TOOD: ui.WorkStatus(numDone int64)
	}
	wgres.Add(1)

	go resultHanlder(ui, &wgres, reschan)

	close(reqchan)
	wg.Wait()
	close(reschan)
	wgres.Wait()
	ui.WorkEnd()

	return nil
}
<|endoftext|>


<commit_before>package v2

import (
	"strings"
	"testing"
)

func TestRepositoryNameRegexp(t *testing.T) {
	for _, testcase := range []struct {
		input string
		err   error
	}{
		{
			input: "short",
		},
		{
			input: "simple/name",
		},
		{
			input: "library/ubuntu",
		},
		{
			input: "docker/stevvooe/app",
		},
		{
			input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
		},
		{
			input: "aa/aa/bb/bb/bb",
		},
		{
			input: "a/a/a/b/b",
			err:   ErrRepositoryNameComponentShort,
		},
		{
			input: "a/a/a/a/",
			err:   ErrRepositoryNameComponentShort,
		},
		{
			input: "foo.com/bar/baz",
		},
		{
			input: "blog.foo.com/bar/baz",
		},
		{
			input: "asdf",
		},
		{
			input: "asdf$$^/aa",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "aa-a/aa",
		},
		{
			input: "aa/aa",
		},
		{
			input: "a-a/a-a",
		},
		{
			input: "a",
			err:   ErrRepositoryNameComponentShort,
		},
		{
			input: "a-/a/a/a",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: strings.Repeat("a", 255),
		},
		{
			input: strings.Repeat("a", 256),
			err:   ErrRepositoryNameLong,
		},
	} {

		failf := func(format string, v ...interface{}) {
			t.Logf(testcase.input+": "+format, v...)
			t.Fail()
		}

		if err := ValidateRepositoryName(testcase.input); err != testcase.err {
			if testcase.err != nil {
				if err != nil {
					failf("unexpected error for invalid repository: got %v, expected %v", err, testcase.err)
				} else {
					failf("expected invalid repository: %v", testcase.err)
				}
			} else {
				if err != nil {
					// Wrong error returned.
					failf("unexpected error validating repository name: %v, expected %v", err, testcase.err)
				} else {
					failf("unexpected error validating repository name: %v", err)
				}
			}
		}
	}
}
<commit_msg>Add more repository name validation test cases<commit_after>package v2

import (
	"strings"
	"testing"
)

func TestRepositoryNameRegexp(t *testing.T) {
	for _, testcase := range []struct {
		input string
		err   error
	}{
		{
			input: "short",
		},
		{
			input: "simple/name",
		},
		{
			input: "library/ubuntu",
		},
		{
			input: "docker/stevvooe/app",
		},
		{
			input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
		},
		{
			input: "aa/aa/bb/bb/bb",
		},
		{
			input: "a/a/a/b/b",
			err:   ErrRepositoryNameComponentShort,
		},
		{
			input: "a/a/a/a/",
			err:   ErrRepositoryNameComponentShort,
		},
		{
			input: "foo.com/bar/baz",
		},
		{
			input: "blog.foo.com/bar/baz",
		},
		{
			input: "asdf",
		},
		{
			input: "asdf$$^/aa",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "aa-a/aa",
		},
		{
			input: "aa/aa",
		},
		{
			input: "a-a/a-a",
		},
		{
			input: "a",
			err:   ErrRepositoryNameComponentShort,
		},
		{
			input: "a-/a/a/a",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: strings.Repeat("a", 255),
		},
		{
			input: strings.Repeat("a", 256),
			err:   ErrRepositoryNameLong,
		},
		{
			input: "-foo/bar",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "foo/bar-",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "foo-/bar",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "foo/-bar",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "_foo/bar",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "foo/bar_",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "____/____",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "_docker/_docker",
			err:   ErrRepositoryNameComponentInvalid,
		},
		{
			input: "docker_/docker_",
			err:   ErrRepositoryNameComponentInvalid,
		},
	} {

		failf := func(format string, v ...interface{}) {
			t.Logf(testcase.input+": "+format, v...)
			t.Fail()
		}

		if err := ValidateRepositoryName(testcase.input); err != testcase.err {
			if testcase.err != nil {
				if err != nil {
					failf("unexpected error for invalid repository: got %v, expected %v", err, testcase.err)
				} else {
					failf("expected invalid repository: %v", testcase.err)
				}
			} else {
				if err != nil {
					// Wrong error returned.
					failf("unexpected error validating repository name: %v, expected %v", err, testcase.err)
				} else {
					failf("unexpected error validating repository name: %v", err)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package relationships

import (
	"errors"
	"github.com/bor3ham/reja/format"
	"github.com/bor3ham/reja/instances"
)

type Pointer struct {
	Data *instances.InstancePointer `json:"data"`
}

type PointerSet struct {
	Data []instances.InstancePointer `json:"data"`
}

// temporary function to flatten list as part of refactor
func flattenMaps(relationMap map[string]map[string][]string) map[string][]string {
	flatMap := map[string][]string{}
	for _, relations := range relationMap {
		for modelType, ids := range relations {
			_, exists := flatMap[modelType]
			if !exists {
				flatMap[modelType] = []string{}
			}
			flatMap[modelType] = append(flatMap[modelType], ids...)
		}
	}
	// unique the list of ids
	distinctMap := map[string][]string{}
	for model, ids := range flatMap {
		distinctMap[model] = []string{}
		distincts := map[string]bool{}
		for _, id := range ids {
			distincts[id] = true
		}
		for id, _ := range distincts {
			distinctMap[model] = append(distinctMap[model], id)
		}
	}
	return distinctMap
}

func AssertPointerSet(val interface{}) PointerSet {
	pageVal, ok := val.(PointerSet)
	if !ok {
		panic("Bad pointer page value")
	}
	return pageVal
}

func ParsePagePointerSet(val interface{}) (PointerSet, error) {
	pageVal, ok := val.(format.Page)
	if !ok {
		panic("Invalid pointer set")
	}
	pointersVal := PointerSet{}
	for _, stringPointer := range pageVal.Data {
		pointer, ok := stringPointer.(map[string]interface{})
		if !ok {
			return PointerSet{}, errors.New("Invalid pointer in pointer set.")
		}

		// parse the id
		pointerId, exists := pointer["id"]
		if !exists {
			return PointerSet{}, errors.New("Invalid pointer in pointer set (missing ID).")
		}
		parsedId, ok := pointerId.(string)
		if !ok {
			return PointerSet{}, errors.New("Invalid pointer in pointer set (bad ID).")
		}

		// parse the type
		pointerType, exists := pointer["type"]
		if !exists {
			return PointerSet{}, errors.New("Invalid pointer in pointer set (missing Type).")
		}
		parsedType, ok := pointerType.(string)
		if !ok {
			return PointerSet{}, errors.New("Invalid pointer in pointer set (bad Type).")
		}

		// valid pointer
		pointersVal.Data = append(pointersVal.Data, instances.InstancePointer{
			Type: parsedType,
			ID:   &parsedId,
		})
	}
	return pointersVal, nil
}
<commit_msg>Remove temp function<commit_after>package relationships

import (
	"errors"
	"github.com/bor3ham/reja/format"
	"github.com/bor3ham/reja/instances"
)

type Pointer struct {
	Data *instances.InstancePointer `json:"data"`
}

type PointerSet struct {
	Data []instances.InstancePointer `json:"data"`
}

func AssertPointerSet(val interface{}) PointerSet {
	pageVal, ok := val.(PointerSet)
	if !ok {
		panic("Bad pointer page value")
	}
	return pageVal
}

func ParsePagePointerSet(val interface{}) (PointerSet, error) {
	pageVal, ok := val.(format.Page)
	if !ok {
		panic("Invalid pointer set")
	}
	pointersVal := PointerSet{}
	for _, stringPointer := range pageVal.Data {
		pointer, ok := stringPointer.(map[string]interface{})
		if !ok {
			return PointerSet{}, errors.New("Invalid pointer in pointer set.")
		}

		// parse the id
		pointerId, exists := pointer["id"]
		if !exists {
			return PointerSet{}, errors.New("Invalid pointer in pointer set (missing ID).")
		}
		parsedId, ok := pointerId.(string)
		if !ok {
			return PointerSet{}, errors.New("Invalid pointer in pointer set (bad ID).")
		}

		// parse the type
		pointerType, exists := pointer["type"]
		if !exists {
			return PointerSet{}, errors.New("Invalid pointer in pointer set (missing Type).")
		}
		parsedType, ok := pointerType.(string)
		if !ok {
			return PointerSet{}, errors.New("Invalid pointer in pointer set (bad Type).")
		}

		// valid pointer
		pointersVal.Data = append(pointersVal.Data, instances.InstancePointer{
			Type: parsedType,
			ID:   &parsedId,
		})
	}
	return pointersVal, nil
}
<|endoftext|>


<commit_before>// Copyright 2013 SteelSeries ApS. All rights reserved.
// No license is given for the use of this source code.

// This package impliments a basic LISP interpretor for embedding in a go program for scripting.
// This file inmpliments data elements
package golisp

import (
    "errors"
    "fmt"
    "strings"
    "unsafe"
)

const (
    ConsCellType = iota
    AlistType
    AlistCellType
    NumberType
    BooleanType
    StringType
    SymbolType
    FunctionType
    PrimitiveType
    ObjectType
)

type Data struct {
    Type    int                // data type
    Car     *Data              // ConsCellType & AlistType
    Cdr     *Data              // ConsCellType & AlistType
    String  string             // StringType & SymbolType
    Number  uint32             // NumberType & BooleanType
    Func    *Function          // FunctionType
    Prim    *PrimitiveFunction // PrimitiveType
    ObjType string             // ObjectType
    Obj     unsafe.Pointer     // ObjectType
}

// Boolean constants

var True *Data = BooleanWithValue(true)
var False *Data = BooleanWithValue(false)

func TypeOf(d *Data) int {
    return d.Type
}

func NilP(d *Data) bool {
    if d == nil {
        return true
    }
    if (PairP(d) || AlistP(d) || DottedPairP(d)) && Car(d) == nil && Cdr(d) == nil {
        return true
    }
    return false
}

func NotNilP(d *Data) bool {
    return !NilP(d)
}

func PairP(d *Data) bool {
    return d == nil || TypeOf(d) == ConsCellType
}

func DottedPairP(d *Data) bool {
    return d == nil || TypeOf(d) == AlistCellType
}

func AlistP(d *Data) bool {
    return d == nil || TypeOf(d) == AlistType
}

func SymbolP(d *Data) bool {
    return d != nil && TypeOf(d) == SymbolType
}

func StringP(d *Data) bool {
    return d != nil && TypeOf(d) == StringType
}

func NumberP(d *Data) bool {
    return d != nil && TypeOf(d) == NumberType
}

func ObjectP(d *Data) bool {
    return d != nil && TypeOf(d) == ObjectType
}

func FunctionP(d *Data) bool {
    return d != nil && TypeOf(d) == FunctionType || TypeOf(d) == PrimitiveType
}

func Cons(car *Data, cdr *Data) *Data {
    return &Data{Type: ConsCellType, Car: car, Cdr: cdr, String: "", Number: 0, Func: nil, Prim: nil}
}

func AppendBang(l *Data, value *Data) *Data {
    if NilP(l) {
        return Cons(value, nil)
    }

    var c *Data
    for c = l; NotNilP(c.Cdr); c = Cdr(c) {
    }

    c.Cdr = Cons(value, nil)

    return l
}

func AppendBangList(l *Data, otherList *Data) *Data {
    if NilP(l) {
        return otherList
    }

    var c *Data
    for c = l; NotNilP(c.Cdr); c = Cdr(c) {
    }

    c.Cdr = otherList

    return l
}

func Append(l *Data, value *Data) *Data {
    if NilP(l) {
        return Cons(value, nil)
    }

    var newList = Copy(l)
    var c *Data
    for c = newList; NotNilP(c.Cdr); c = Cdr(c) {
    }

    c.Cdr = Cons(value, nil)

    return newList
}

func AppendList(l *Data, otherList *Data) *Data {
    if NilP(l) {
        return otherList
    }

    var newList = Copy(l)
    var c *Data
    for c = newList; NotNilP(c.Cdr); c = Cdr(c) {
    }

    c.Cdr = otherList

    return newList
}

func Acons(car *Data, cdr *Data, alist *Data) *Data {
    pair, _ := Assoc(car, alist)
    if NilP(pair) {
        cell := &Data{Type: AlistCellType, Car: car, Cdr: cdr, String: "", Number: 0, Func: nil, Prim: nil}
        return &Data{Type: AlistType, Car: cell, Cdr: alist, String: "", Number: 0, Func: nil, Prim: nil}
    } else {
        pair.Cdr = cdr
        return alist
    }
}

func Alist(d *Data) *Data {
    if NilP(d) {
        return nil
    }

    if PairP(d) {
        return Acons(Caar(d), Cdar(d), Alist(Cdr(d)))
    }

    return d
}

func InternalMakeList(c ...*Data) *Data {
    return ArrayToList(c)
}

func EmptyCons() *Data {
    return Cons(nil, nil)
}

func NumberWithValue(n uint32) *Data {
    return &Data{Type: NumberType, Car: nil, Cdr: nil, String: "", Number: n, Func: nil, Prim: nil}
}

func BooleanWithValue(b bool) *Data {
    var num int = 0
    if b {
        num = 1
    }
    return &Data{Type: BooleanType, Car: nil, Cdr: nil, String: "", Number: uint32(num), Func: nil, Prim: nil}
}

func StringWithValue(s string) *Data {
    return &Data{Type: StringType, Car: nil, Cdr: nil, String: s, Number: 0, Func: nil, Prim: nil}
}

func SymbolWithName(s string) *Data {
    return &Data{Type: SymbolType, Car: nil, Cdr: nil, String: s, Number: 0, Func: nil, Prim: nil}
}

func FunctionWithNameParamsBodyAndParent(name string, params *Data, body *Data, parentEnv *SymbolTableFrame) *Data {
    return &Data{Type: FunctionType, Car: nil, Cdr: nil, String: "", Number: 0, Func: MakeFunction(name, params, body, parentEnv), Prim: nil}
}

func PrimitiveWithNameAndFunc(name string, f *PrimitiveFunction) *Data {
    return &Data{Type: PrimitiveType, Car: nil, Cdr: nil, String: "", Number: 0, Func: nil, Prim: f}
}

func ObjectWithTypeAndValue(typeName string, o unsafe.Pointer) *Data {
    return &Data{Type: ObjectType, Car: nil, Cdr: nil, String: "", Number: 0, Func: nil, Prim: nil, ObjType: typeName, Obj: o}
}

func NumericValue(d *Data) uint32 {
    if d == nil {
        return 0
    }

    if d.Type == NumberType {
        return d.Number
    }

    return 0
}

func StringValue(d *Data) string {
    if d == nil {
        return ""
    }

    if d.Type == StringType || d.Type == SymbolType {
        return d.String
    }

    return ""
}

func BooleanValue(d *Data) bool {
    if d == nil {
        return false
    }

    if d.Type == BooleanType {
        if d.Number == 0 {
            return false
        } else {
            return true
        }
    }

    return true
}

func TypeOfObject(d *Data) (oType string) {
    if d == nil {
        return
    }

    if d.Type == ObjectType {
        return d.ObjType
    }

    return
}

func ObjectValue(d *Data) (p unsafe.Pointer) {
    if d == nil {
        return
    }

    if d.Type == ObjectType {
        return d.Obj
    }

    return
}

func Length(d *Data) int {
    if d == nil {
        return 0
    }

    if d.Type == ConsCellType || d.Type == AlistType {
        return 1 + Length(d.Cdr)
    }

    return 0
}

func Reverse(d *Data) (result *Data) {
    if d == nil {
        return nil
    }

    if !PairP(d) {
        return d
    }

    var l *Data = nil
    for c := d; NotNilP(c); c = Cdr(c) {
        l = Cons(Car(c), l)
    }

    return l
}

func Assoc(key *Data, alist *Data) (result *Data, err error) {
    for c := alist; NotNilP(c); c = Cdr(c) {
        pair := Car(c)
        if !DottedPairP(pair) && !PairP(pair) {
            err = errors.New("Alist key can not be a list")
            return
        }
        if IsEqual(Car(pair), key) {
            result = pair
            return
        }
    }
    return
}

func Copy(d *Data) *Data {
    if d == nil {
        return d
    }

    switch d.Type {
    case AlistType:
        {
            alist := Acons(Copy(Caar(d)), Copy(Cdar(d)), nil)
            for c := Cdr(d); NotNilP(c); c = Cdr(c) {
                alist = Acons(Copy(Caar(c)), Copy(Cdar(c)), alist)
            }
            return alist
        }
    case ConsCellType:
        {
            if NilP(d) {
                return d
            }

            return Cons(Copy(Car(d)), Copy(Cdr(d)))
        }
    }

    return d
}

func IsEqual(d *Data, o *Data) bool {
    if d == o {
        return true
    }

    if d == nil || o == nil {
        return false
    }

    if AlistP(d) {
        if !AlistP(o) && !PairP(o) {
            return false
        }
    } else if TypeOf(d) == AlistCellType {
        if TypeOf(o) != ConsCellType && TypeOf(o) != AlistCellType {
            return false
        }
    } else if TypeOf(o) != TypeOf(d) {
        return false
    }

    if PairP(d) {
        if Length(d) != Length(o) {
            return false
        }
        for a1, a2 := d, o; NotNilP(a1); a1, a2 = Cdr(a1), Cdr(a2) {
            if !IsEqual(Car(a1), Car(a2)) {
                return false
            }
        }
        return true
    }

    if AlistP(d) {
        if Length(d) != Length(o) {
            return false
        }
        for c := d; NotNilP(c); c = Cdr(c) {
            otherPair, err := Assoc(Caar(c), o)
            if err != nil || NilP(otherPair) || !IsEqual(Cdar(c), Cdr(otherPair)) {
                return false
            }
        }
        return true
    }

    if TypeOf(d) == AlistCellType {
        return IsEqual(Car(d), Car(o)) && IsEqual(Cdr(d), Cdr(o))
    }

    return *d == *o
}

func escapeQuotes(str string) string {
    buffer := make([]rune, 0, 10)
    for _, ch := range str {
        if rune(ch) == '"' {
            buffer = append(buffer, '\\')
        }
        buffer = append(buffer, rune(ch))
    }
    return string(buffer)
}

func String(d *Data) string {
    if d == nil {
        return "()"
    }

    switch d.Type {
    case ConsCellType:
        {
            if NilP(Car(d)) && NilP(Cdr(d)) {
                return "()"
            }
            var c *Data = d

            contents := make([]string, 0, Length(d))
            for NotNilP(c) && PairP(c) {
                contents = append(contents, String(Car(c)))
                c = Cdr(c)
            }
            if c == nil {
                if SymbolP(Car(d)) && StringValue(Car(d)) == "quote" {
                    if len(contents) == 1 {
                        return fmt.Sprintf("'()")
                    } else {
                        return fmt.Sprintf("'%s", contents[1])
                    }
                } else {
                    return fmt.Sprintf("(%s)", strings.Join(contents, " "))
                }
            } else {
                return fmt.Sprintf("(%s . %s)", strings.Join(contents, " "), String(c))
            }
        }
    case AlistType:
        {
            if NilP(Car(d)) && NilP(Cdr(d)) {
                return "()"
            }
            contents := make([]string, 0, Length(d))
            for c := d; NotNilP(c); c = Cdr(c) {
                contents = append(contents, String(Car(c)))
            }
            return fmt.Sprintf("(%s)", strings.Join(contents, " "))
        }
    case AlistCellType:
        return fmt.Sprintf("(%s . %s)", String(Car(d)), String(Cdr(d)))
    case NumberType:
        return fmt.Sprintf("%d", d.Number)
    case BooleanType:
        if d.Number == 0 {
            return "#f"
        } else {
            return "#t"
        }
    case StringType:
        return fmt.Sprintf(`"%s"`, escapeQuotes(d.String))
    case SymbolType:
        return d.String
    case FunctionType:
        return fmt.Sprintf("<function: %s>", d.Func.Name)
    case PrimitiveType:
        return d.Prim.String()
    case ObjectType:
        return fmt.Sprintf("<opaque Go object of type %s : 0x%x>", d.ObjType, (*uint64)(d.Obj))
    }

    return ""
}

func Eval(d *Data, env *SymbolTableFrame) (result *Data, err error) {
    if d == nil {
        return
    }

    switch d.Type {
    case ConsCellType:
        {
            var function *Data
            function, err = Eval(Car(d), env)
            if err != nil {
                return
            }
            args := Cdr(d)
            result, err = Apply(function, args, env)
            if err != nil {
                err = errors.New(fmt.Sprintf("Evaling %s. %s", String(d), err))
                return
            }
            return
        }
    case SymbolType:
        result = env.ValueOf(d)
        return
    }

    return d, nil
}

func Apply(function *Data, args *Data, env *SymbolTableFrame) (result *Data, err error) {
    if function == nil {
        err = errors.New("Nil when function expected.")
        return
    }
    switch function.Type {
    case FunctionType:
        return function.Func.Apply(args, env)
    case PrimitiveType:
        return function.Prim.Apply(args, env)
    }
    return
}
<commit_msg>Added an internal boolean predicate.<commit_after>// Copyright 2013 SteelSeries ApS. All rights reserved.
// No license is given for the use of this source code.

// This package impliments a basic LISP interpretor for embedding in a go program for scripting.
// This file inmpliments data elements
package golisp

import (
    "errors"
    "fmt"
    "strings"
    "unsafe"
)

const (
    ConsCellType = iota
    AlistType
    AlistCellType
    NumberType
    BooleanType
    StringType
    SymbolType
    FunctionType
    PrimitiveType
    ObjectType
)

type Data struct {
    Type    int                // data type
    Car     *Data              // ConsCellType & AlistType
    Cdr     *Data              // ConsCellType & AlistType
    String  string             // StringType & SymbolType
    Number  uint32             // NumberType & BooleanType
    Func    *Function          // FunctionType
    Prim    *PrimitiveFunction // PrimitiveType
    ObjType string             // ObjectType
    Obj     unsafe.Pointer     // ObjectType
}

// Boolean constants

var True *Data = BooleanWithValue(true)
var False *Data = BooleanWithValue(false)

func TypeOf(d *Data) int {
    return d.Type
}

func NilP(d *Data) bool {
    if d == nil {
        return true
    }
    if (PairP(d) || AlistP(d) || DottedPairP(d)) && Car(d) == nil && Cdr(d) == nil {
        return true
    }
    return false
}

func NotNilP(d *Data) bool {
    return !NilP(d)
}

func PairP(d *Data) bool {
    return d == nil || TypeOf(d) == ConsCellType
}

func DottedPairP(d *Data) bool {
    return d == nil || TypeOf(d) == AlistCellType
}

func AlistP(d *Data) bool {
    return d == nil || TypeOf(d) == AlistType
}

func BooleanP(d *Data) bool {
    return d != nil && TypeOf(d) == BooleanType
}

func SymbolP(d *Data) bool {
    return d != nil && TypeOf(d) == SymbolType
}

func StringP(d *Data) bool {
    return d != nil && TypeOf(d) == StringType
}

func NumberP(d *Data) bool {
    return d != nil && TypeOf(d) == NumberType
}

func ObjectP(d *Data) bool {
    return d != nil && TypeOf(d) == ObjectType
}

func FunctionP(d *Data) bool {
    return d != nil && TypeOf(d) == FunctionType || TypeOf(d) == PrimitiveType
}

func Cons(car *Data, cdr *Data) *Data {
    return &Data{Type: ConsCellType, Car: car, Cdr: cdr, String: "", Number: 0, Func: nil, Prim: nil}
}

func AppendBang(l *Data, value *Data) *Data {
    if NilP(l) {
        return Cons(value, nil)
    }

    var c *Data
    for c = l; NotNilP(c.Cdr); c = Cdr(c) {
    }

    c.Cdr = Cons(value, nil)

    return l
}

func AppendBangList(l *Data, otherList *Data) *Data {
    if NilP(l) {
        return otherList
    }

    var c *Data
    for c = l; NotNilP(c.Cdr); c = Cdr(c) {
    }

    c.Cdr = otherList

    return l
}

func Append(l *Data, value *Data) *Data {
    if NilP(l) {
        return Cons(value, nil)
    }

    var newList = Copy(l)
    var c *Data
    for c = newList; NotNilP(c.Cdr); c = Cdr(c) {
    }

    c.Cdr = Cons(value, nil)

    return newList
}

func AppendList(l *Data, otherList *Data) *Data {
    if NilP(l) {
        return otherList
    }

    var newList = Copy(l)
    var c *Data
    for c = newList; NotNilP(c.Cdr); c = Cdr(c) {
    }

    c.Cdr = otherList

    return newList
}

func Acons(car *Data, cdr *Data, alist *Data) *Data {
    pair, _ := Assoc(car, alist)
    if NilP(pair) {
        cell := &Data{Type: AlistCellType, Car: car, Cdr: cdr, String: "", Number: 0, Func: nil, Prim: nil}
        return &Data{Type: AlistType, Car: cell, Cdr: alist, String: "", Number: 0, Func: nil, Prim: nil}
    } else {
        pair.Cdr = cdr
        return alist
    }
}

func Alist(d *Data) *Data {
    if NilP(d) {
        return nil
    }

    if PairP(d) {
        return Acons(Caar(d), Cdar(d), Alist(Cdr(d)))
    }

    return d
}

func InternalMakeList(c ...*Data) *Data {
    return ArrayToList(c)
}

func EmptyCons() *Data {
    return Cons(nil, nil)
}

func NumberWithValue(n uint32) *Data {
    return &Data{Type: NumberType, Car: nil, Cdr: nil, String: "", Number: n, Func: nil, Prim: nil}
}

func BooleanWithValue(b bool) *Data {
    var num int = 0
    if b {
        num = 1
    }
    return &Data{Type: BooleanType, Car: nil, Cdr: nil, String: "", Number: uint32(num), Func: nil, Prim: nil}
}

func StringWithValue(s string) *Data {
    return &Data{Type: StringType, Car: nil, Cdr: nil, String: s, Number: 0, Func: nil, Prim: nil}
}

func SymbolWithName(s string) *Data {
    return &Data{Type: SymbolType, Car: nil, Cdr: nil, String: s, Number: 0, Func: nil, Prim: nil}
}

func FunctionWithNameParamsBodyAndParent(name string, params *Data, body *Data, parentEnv *SymbolTableFrame) *Data {
    return &Data{Type: FunctionType, Car: nil, Cdr: nil, String: "", Number: 0, Func: MakeFunction(name, params, body, parentEnv), Prim: nil}
}

func PrimitiveWithNameAndFunc(name string, f *PrimitiveFunction) *Data {
    return &Data{Type: PrimitiveType, Car: nil, Cdr: nil, String: "", Number: 0, Func: nil, Prim: f}
}

func ObjectWithTypeAndValue(typeName string, o unsafe.Pointer) *Data {
    return &Data{Type: ObjectType, Car: nil, Cdr: nil, String: "", Number: 0, Func: nil, Prim: nil, ObjType: typeName, Obj: o}
}

func NumericValue(d *Data) uint32 {
    if d == nil {
        return 0
    }

    if d.Type == NumberType {
        return d.Number
    }

    return 0
}

func StringValue(d *Data) string {
    if d == nil {
        return ""
    }

    if d.Type == StringType || d.Type == SymbolType {
        return d.String
    }

    return ""
}

func BooleanValue(d *Data) bool {
    if d == nil {
        return false
    }

    if d.Type == BooleanType {
        if d.Number == 0 {
            return false
        } else {
            return true
        }
    }

    return true
}

func TypeOfObject(d *Data) (oType string) {
    if d == nil {
        return
    }

    if d.Type == ObjectType {
        return d.ObjType
    }

    return
}

func ObjectValue(d *Data) (p unsafe.Pointer) {
    if d == nil {
        return
    }

    if d.Type == ObjectType {
        return d.Obj
    }

    return
}

func Length(d *Data) int {
    if d == nil {
        return 0
    }

    if d.Type == ConsCellType || d.Type == AlistType {
        return 1 + Length(d.Cdr)
    }

    return 0
}

func Reverse(d *Data) (result *Data) {
    if d == nil {
        return nil
    }

    if !PairP(d) {
        return d
    }

    var l *Data = nil
    for c := d; NotNilP(c); c = Cdr(c) {
        l = Cons(Car(c), l)
    }

    return l
}

func Assoc(key *Data, alist *Data) (result *Data, err error) {
    for c := alist; NotNilP(c); c = Cdr(c) {
        pair := Car(c)
        if !DottedPairP(pair) && !PairP(pair) {
            err = errors.New("Alist key can not be a list")
            return
        }
        if IsEqual(Car(pair), key) {
            result = pair
            return
        }
    }
    return
}

func Copy(d *Data) *Data {
    if d == nil {
        return d
    }

    switch d.Type {
    case AlistType:
        {
            alist := Acons(Copy(Caar(d)), Copy(Cdar(d)), nil)
            for c := Cdr(d); NotNilP(c); c = Cdr(c) {
                alist = Acons(Copy(Caar(c)), Copy(Cdar(c)), alist)
            }
            return alist
        }
    case ConsCellType:
        {
            if NilP(d) {
                return d
            }

            return Cons(Copy(Car(d)), Copy(Cdr(d)))
        }
    }

    return d
}

func IsEqual(d *Data, o *Data) bool {
    if d == o {
        return true
    }

    if d == nil || o == nil {
        return false
    }

    if AlistP(d) {
        if !AlistP(o) && !PairP(o) {
            return false
        }
    } else if TypeOf(d) == AlistCellType {
        if TypeOf(o) != ConsCellType && TypeOf(o) != AlistCellType {
            return false
        }
    } else if TypeOf(o) != TypeOf(d) {
        return false
    }

    if PairP(d) {
        if Length(d) != Length(o) {
            return false
        }
        for a1, a2 := d, o; NotNilP(a1); a1, a2 = Cdr(a1), Cdr(a2) {
            if !IsEqual(Car(a1), Car(a2)) {
                return false
            }
        }
        return true
    }

    if AlistP(d) {
        if Length(d) != Length(o) {
            return false
        }
        for c := d; NotNilP(c); c = Cdr(c) {
            otherPair, err := Assoc(Caar(c), o)
            if err != nil || NilP(otherPair) || !IsEqual(Cdar(c), Cdr(otherPair)) {
                return false
            }
        }
        return true
    }

    if TypeOf(d) == AlistCellType {
        return IsEqual(Car(d), Car(o)) && IsEqual(Cdr(d), Cdr(o))
    }

    return *d == *o
}

func escapeQuotes(str string) string {
    buffer := make([]rune, 0, 10)
    for _, ch := range str {
        if rune(ch) == '"' {
            buffer = append(buffer, '\\')
        }
        buffer = append(buffer, rune(ch))
    }
    return string(buffer)
}

func String(d *Data) string {
    if d == nil {
        return "()"
    }

    switch d.Type {
    case ConsCellType:
        {
            if NilP(Car(d)) && NilP(Cdr(d)) {
                return "()"
            }
            var c *Data = d

            contents := make([]string, 0, Length(d))
            for NotNilP(c) && PairP(c) {
                contents = append(contents, String(Car(c)))
                c = Cdr(c)
            }
            if c == nil {
                if SymbolP(Car(d)) && StringValue(Car(d)) == "quote" {
                    if len(contents) == 1 {
                        return fmt.Sprintf("'()")
                    } else {
                        return fmt.Sprintf("'%s", contents[1])
                    }
                } else {
                    return fmt.Sprintf("(%s)", strings.Join(contents, " "))
                }
            } else {
                return fmt.Sprintf("(%s . %s)", strings.Join(contents, " "), String(c))
            }
        }
    case AlistType:
        {
            if NilP(Car(d)) && NilP(Cdr(d)) {
                return "()"
            }
            contents := make([]string, 0, Length(d))
            for c := d; NotNilP(c); c = Cdr(c) {
                contents = append(contents, String(Car(c)))
            }
            return fmt.Sprintf("(%s)", strings.Join(contents, " "))
        }
    case AlistCellType:
        return fmt.Sprintf("(%s . %s)", String(Car(d)), String(Cdr(d)))
    case NumberType:
        return fmt.Sprintf("%d", d.Number)
    case BooleanType:
        if d.Number == 0 {
            return "#f"
        } else {
            return "#t"
        }
    case StringType:
        return fmt.Sprintf(`"%s"`, escapeQuotes(d.String))
    case SymbolType:
        return d.String
    case FunctionType:
        return fmt.Sprintf("<function: %s>", d.Func.Name)
    case PrimitiveType:
        return d.Prim.String()
    case ObjectType:
        return fmt.Sprintf("<opaque Go object of type %s : 0x%x>", d.ObjType, (*uint64)(d.Obj))
    }

    return ""
}

func Eval(d *Data, env *SymbolTableFrame) (result *Data, err error) {
    if d == nil {
        return
    }

    switch d.Type {
    case ConsCellType:
        {
            var function *Data
            function, err = Eval(Car(d), env)
            if err != nil {
                return
            }
            args := Cdr(d)
            result, err = Apply(function, args, env)
            if err != nil {
                err = errors.New(fmt.Sprintf("Evaling %s. %s", String(d), err))
                return
            }
            return
        }
    case SymbolType:
        result = env.ValueOf(d)
        return
    }

    return d, nil
}

func Apply(function *Data, args *Data, env *SymbolTableFrame) (result *Data, err error) {
    if function == nil {
        err = errors.New("Nil when function expected.")
        return
    }
    switch function.Type {
    case FunctionType:
        return function.Func.Apply(args, env)
    case PrimitiveType:
        return function.Prim.Apply(args, env)
    }
    return
}
<|endoftext|>


<commit_before>package endpoints

import (
	"../config"
	"../models"

	"database/sql"
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
)

// RegisterReleaseHandlers attaches the closures generated by each function defined below
// to handle incoming requests to the appropriate endpoint using a subrouter with an
// appropriate prefix, specified in main.
func RegisterReleaseHandlers(r *mux.Router, db *sql.DB, cfg *config.Config) {
	r.HandleFunc("/", listReleases(db, cfg)).Methods("GET")
	r.HandleFunc("/", createRelease(db, cfg)).Methods("POST")
	r.HandleFunc("/{releaseId}", getRelease(db, cfg)).Methods("GET")
	r.HandleFunc("/{releaseId}", updateRelease(db, cfg)).Methods("PUT")
	r.HandleFunc("/{releaseId}", deleteRelease(db, cfg)).Methods("DELETE")
}

// GET /projects/{projectId}/releases

type listReleasesRequest struct {
	ProjectID int
	Ordering  string
}

type listReleasesResponse struct {
	Error    *string          `json:"error"`
	Releases []models.Release `json:"releases"`
}

// listReleases produces a list of all releases under a given project.
// It accepts an "ordering" parameter, which can be either "newest" or "oldest"
// to specify which should come first.
func listReleases(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		pid := mux.Vars(r)["projectId"]
		projectId, parseErr := strconv.Atoi(pid)
		request := listReleasesRequest{0, "newest"}
		orderings, found := r.URL.Query()["ordering"]
		if found {
			request.Ordering = orderings[0]
		}

		encoder := json.NewEncoder(w)
		if parseErr != nil {
			errMsg := "projectId must be an integer ID."
			encoder.Encode(listReleasesResponse{&errMsg, []models.Release{}})
			return
		}
		request.ProjectID = projectId
		// TODO - List all releases under the project from the database.
		encoder.Encode(listReleasesResponse{nil, []models.Release{}})
	}
}

// POST /projects/{projectId}/releases

type createReleaseRequest struct {
	Chapter string               `json:"chapter"`
	Version int                  `json:"version"`
	Status  models.ReleaseStatus `json:"status"`
}

type createReleaseResponse struct {
	Error   *string `json:"error"`
	Success bool    `json:"success"`
	Id      int     `json:"id"`
}

// createRelease inserts a new release into the database.
func createRelease(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		request := createReleaseRequest{}
		decoder := json.NewDecoder(r.Body)
		defer r.Body.Close()
		decodeErr := decoder.Decode(&request)

		encoder := json.NewEncoder(w)
		if decodeErr != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "JSON format error or missing field detected."
			encoder.Encode(createReleaseResponse{&errMsg, false, 0})
			return
		}
		// TODO - Insert the release into the DB and update its ID etc.
		encoder.Encode(createReleaseResponse{nil, true, 1})
	}
}

// GET /projects/{projectId}/releases/{releaseId}

type getReleaseRequest struct {
	ProjectID int
	ReleaseID int
}

type getReleaseResponse struct {
	Error       *string              `json:"error"`
	ProjectName string               `json:"projectName"`
	Chapter     string               `json:"chapter"`
	GroupName   string               `json:"groupName"`
	Checksum    string               `json:"checksum"`
	Version     int                  `json:"version"`
	Status      models.ReleaseStatus `json:"status"`
	ReleasedOn  time.Time            `json:"releasedOn"`
}

// getRelease obtains information about a specific release.
func getRelease(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		request := getReleaseRequest{}
		vars := mux.Vars(r)
		pid := vars["projectId"]
		rid := vars["releaseId"]
		projectId, parseErr1 := strconv.Atoi(pid)
		releaseId, parseErr2 := strconv.Atoi(rid)

		encoder := json.NewEncoder(w)
		if parseErr1 != nil || parseErr2 != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "projectId and releaseId must be integer IDs."
			encoder.Encode(getReleaseResponse{&errMsg, "", "", "", "", 0, "", time.Now()})
			return
		}
		request.ProjectID = projectId
		request.ReleaseID = releaseId
		// TODO - Retrieve the release from the DB.
		encoder.Encode(getReleaseResponse{
			nil,
			"project",
			"ch001",
			"ims",
			"abc123",
			1,
			models.RStatusReleased,
			time.Now(),
		})
	}
}

// PUT /projects/{projectId}/releases/{releaseId}

type updateReleaseRequest struct {
	ProjectID int                  // Pulled from the URL params
	ReleaseID int                  // Pulled from the URL params
	Chapter   string               `json:"chapter"`
	Version   int                  `json:"version"`
	Status    models.ReleaseStatus `json:"status"`
}

type updateReleaseResponse struct {
	Error   *string `json:"error"`
	Success bool    `json:"success"`
}

// updateRelease updates the chapter, version, and status of a release.
func updateRelease(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		request := updateReleaseRequest{}
		vars := mux.Vars(r)
		pid := vars["projectId"]
		rid := vars["releaseId"]
		projectId, parseErr1 := strconv.Atoi(pid)
		releaseId, parseErr2 := strconv.Atoi(rid)
		decoder := json.NewDecoder(r.Body)
		defer r.Body.Close()
		decodeErr := decoder.Decode(&request)

		encoder := json.NewEncoder(w)
		if parseErr1 != nil || parseErr2 != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "projectId and releaseId must both be integer IDs."
			encoder.Encode(updateReleaseResponse{&errMsg, false})
			return
		}
		request.ProjectID = projectId
		request.ReleaseID = releaseId
		if decodeErr != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "Failed to decode JSON in the request body."
			encoder.Encode(updateReleaseResponse{&errMsg, false})
			return
		}
		// TODO - Update the release in the DB.
		encoder.Encode(updateReleaseResponse{nil, true})
	}
}

// DELETE /projects/{projectId}/releases/{releaseId}

type deleteReleaseRequest struct {
	ProjectID int
	ReleaseID int
}

type deleteReleaseResponse struct {
	Error   *string `json:"error"`
	Success bool    `json:"success"`
}

// deleteRelease deletes a release from the DB and also all associated pages.
func deleteRelease(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		request := deleteReleaseRequest{}
		vars := mux.Vars(r)
		pid := vars["projectId"]
		rid := vars["releaseId"]
		projectId, parseErr1 := strconv.Atoi(pid)
		releaseId, parseErr2 := strconv.Atoi(rid)

		encoder := json.NewEncoder(w)
		if parseErr1 != nil || parseErr2 != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "projectId and releaseId must both be integer IDs."
			encoder.Encode(deleteReleaseResponse{&errMsg, false})
			return
		}
		request.ProjectID = projectId
		request.ReleaseID = releaseId
		// TODO - Delete the release from the DB.
		encoder.Encode(deleteReleaseResponse{nil, true})
	}
}
<commit_msg>Finished implementing the create release endpoint<commit_after>package endpoints

import (
	"../config"
	"../models"

	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
)

// RegisterReleaseHandlers attaches the closures generated by each function defined below
// to handle incoming requests to the appropriate endpoint using a subrouter with an
// appropriate prefix, specified in main.
func RegisterReleaseHandlers(r *mux.Router, db *sql.DB, cfg *config.Config) {
	r.HandleFunc("/", listReleases(db, cfg)).Methods("GET")
	r.HandleFunc("/", createRelease(db, cfg)).Methods("POST")
	r.HandleFunc("/{releaseId}", getRelease(db, cfg)).Methods("GET")
	r.HandleFunc("/{releaseId}", updateRelease(db, cfg)).Methods("PUT")
	r.HandleFunc("/{releaseId}", deleteRelease(db, cfg)).Methods("DELETE")
}

// GET /projects/{projectId}/releases

type listReleasesRequest struct {
	ProjectID int
	Ordering  string
}

type listReleasesResponse struct {
	Error    *string          `json:"error"`
	Releases []models.Release `json:"releases"`
}

// listReleases produces a list of all releases under a given project.
// It accepts an "ordering" parameter, which can be either "newest" or "oldest"
// to specify which should come first.
func listReleases(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		pid := mux.Vars(r)["projectId"]
		projectId, parseErr := strconv.Atoi(pid)
		request := listReleasesRequest{0, "newest"}
		orderings, found := r.URL.Query()["ordering"]
		if found {
			request.Ordering = orderings[0]
		}

		encoder := json.NewEncoder(w)
		if parseErr != nil {
			errMsg := "projectId must be an integer ID."
			encoder.Encode(listReleasesResponse{&errMsg, []models.Release{}})
			return
		}
		request.ProjectID = projectId
		// TODO - List all releases under the project from the database.
		encoder.Encode(listReleasesResponse{nil, []models.Release{}})
	}
}

// POST /projects/{projectId}/releases

type createReleaseRequest struct {
	ProjectID int                  // Pulled from the URL parameters
	Chapter   string               `json:"chapter"`
	Version   int                  `json:"version"`
	Status    models.ReleaseStatus `json:"status"`
}

type createReleaseResponse struct {
	Error   *string `json:"error"`
	Success bool    `json:"success"`
	Id      int     `json:"id"`
}

// createRelease inserts a new release into the database.
func createRelease(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		request := createReleaseRequest{}
		projectId, parseErr := strconv.Atoi(mux.Vars(r)["projectId"])
		decoder := json.NewDecoder(r.Body)
		defer r.Body.Close()
		decodeErr := decoder.Decode(&request)

		encoder := json.NewEncoder(w)
		if parseErr != nil {
			fmt.Println("[---] Parse error:", parseErr)
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "projectId must be an integer project ID."
			encoder.Encode(createReleaseResponse{&errMsg, false, -1})
			return
		}
		request.ProjectID = projectId
		if decodeErr != nil {
			fmt.Println("[---] Decode error:", decodeErr)
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "JSON format error or missing field detected."
			encoder.Encode(createReleaseResponse{&errMsg, false, -1})
			return
		}
		release := models.NewRelease(request.ProjectID, request.Version, request.Chapter)
		release.Status = request.Status
		insertErr := release.Save(db)
		if insertErr != nil {
			fmt.Println("[---] Insert error:", insertErr)
			w.WriteHeader(http.StatusInternalServerError)
			errMsg := "Could not create new release. Please check that the status is valid or try again later."
			encoder.Encode(createReleaseResponse{&errMsg, false, -1})
			return
		}
		encoder.Encode(createReleaseResponse{nil, true, release.Id})
	}
}

// GET /projects/{projectId}/releases/{releaseId}

type getReleaseRequest struct {
	ProjectID int
	ReleaseID int
}

type getReleaseResponse struct {
	Error       *string              `json:"error"`
	ProjectName string               `json:"projectName"`
	Chapter     string               `json:"chapter"`
	GroupName   string               `json:"groupName"`
	Checksum    string               `json:"checksum"`
	Version     int                  `json:"version"`
	Status      models.ReleaseStatus `json:"status"`
	ReleasedOn  time.Time            `json:"releasedOn"`
}

// getRelease obtains information about a specific release.
func getRelease(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		request := getReleaseRequest{}
		vars := mux.Vars(r)
		pid := vars["projectId"]
		rid := vars["releaseId"]
		projectId, parseErr1 := strconv.Atoi(pid)
		releaseId, parseErr2 := strconv.Atoi(rid)

		encoder := json.NewEncoder(w)
		if parseErr1 != nil || parseErr2 != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "projectId and releaseId must be integer IDs."
			encoder.Encode(getReleaseResponse{&errMsg, "", "", "", "", 0, "", time.Now()})
			return
		}
		request.ProjectID = projectId
		request.ReleaseID = releaseId
		// TODO - Retrieve the release from the DB.
		encoder.Encode(getReleaseResponse{
			nil,
			"project",
			"ch001",
			"ims",
			"abc123",
			1,
			models.RStatusReleased,
			time.Now(),
		})
	}
}

// PUT /projects/{projectId}/releases/{releaseId}

type updateReleaseRequest struct {
	ProjectID int                  // Pulled from the URL params
	ReleaseID int                  // Pulled from the URL params
	Chapter   string               `json:"chapter"`
	Version   int                  `json:"version"`
	Status    models.ReleaseStatus `json:"status"`
}

type updateReleaseResponse struct {
	Error   *string `json:"error"`
	Success bool    `json:"success"`
}

// updateRelease updates the chapter, version, and status of a release.
func updateRelease(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		request := updateReleaseRequest{}
		vars := mux.Vars(r)
		pid := vars["projectId"]
		rid := vars["releaseId"]
		projectId, parseErr1 := strconv.Atoi(pid)
		releaseId, parseErr2 := strconv.Atoi(rid)
		decoder := json.NewDecoder(r.Body)
		defer r.Body.Close()
		decodeErr := decoder.Decode(&request)

		encoder := json.NewEncoder(w)
		if parseErr1 != nil || parseErr2 != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "projectId and releaseId must both be integer IDs."
			encoder.Encode(updateReleaseResponse{&errMsg, false})
			return
		}
		request.ProjectID = projectId
		request.ReleaseID = releaseId
		if decodeErr != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "Failed to decode JSON in the request body."
			encoder.Encode(updateReleaseResponse{&errMsg, false})
			return
		}
		// TODO - Update the release in the DB.
		encoder.Encode(updateReleaseResponse{nil, true})
	}
}

// DELETE /projects/{projectId}/releases/{releaseId}

type deleteReleaseRequest struct {
	ProjectID int
	ReleaseID int
}

type deleteReleaseResponse struct {
	Error   *string `json:"error"`
	Success bool    `json:"success"`
}

// deleteRelease deletes a release from the DB and also all associated pages.
func deleteRelease(db *sql.DB, cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		request := deleteReleaseRequest{}
		vars := mux.Vars(r)
		pid := vars["projectId"]
		rid := vars["releaseId"]
		projectId, parseErr1 := strconv.Atoi(pid)
		releaseId, parseErr2 := strconv.Atoi(rid)

		encoder := json.NewEncoder(w)
		if parseErr1 != nil || parseErr2 != nil {
			w.WriteHeader(http.StatusBadRequest)
			errMsg := "projectId and releaseId must both be integer IDs."
			encoder.Encode(deleteReleaseResponse{&errMsg, false})
			return
		}
		request.ProjectID = projectId
		request.ReleaseID = releaseId
		// TODO - Delete the release from the DB.
		encoder.Encode(deleteReleaseResponse{nil, true})
	}
}
<|endoftext|>


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

// This file defines the EditSet interface and a default implementation,
// including the NewEditSet method.  All Go refactorings/transformations return
// an EditSet, which describes what file(s) will be affected by a refactoring
// and exactly what characters in those files will be affected.

// Contributors: Jeff Overbey, Reed Allman

package doctor

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"syscall"
)

// An EditSet is a collection of changes to be made to a text file.  Each edit
// is comprised of an offset, a length, and a replacement string.
//
// Each edit replaces 0 or more characters at a given offset with a given
// string.  Characters can be inserted by using a position with length 0;
// characters can be deleted by using "" for the replacement string.
//
// Edits are added to an EditSet via the Add method, and the edits in an
// EditSet can be applied to an input by invoking the ApplyTo method or
// one of the utility functions ApplyToString, ApplyToFile, or ApplyToReader.
type EditSet interface {
	// Add inserts an edit into this editSet, returning an error if the
	// edit has a negative offset or overlaps an edit previously added to
	// this EditSet.  If the EditSet is read-only (e.g., Patch implements
	// EditSet but cannot be modified), an error will always be returned.
	Add(position OffsetLength, replacement string) error

	// ApplyTo reads from the given reader, applying the edits in this
	// editSet as it reads, and writes the output to the given writer.
	// It returns an error if there are edits with offsets beyond the end
	// of the input or some other error occurs, such as an I/O error.
	ApplyTo(in io.Reader, out io.Writer) error

	// CreatePatch creates a Patch from this EditSet.  A Patch is itself
	// an EditSet -- it can be applied using ApplyTo -- or it can be output
	// as a unified diff by invoking the Patch's Write method.
	CreatePatch(in io.Reader) (*Patch, error)

	// String returns a human-readable description of this EditSet.
	String() string
}

type edit struct {
	OffsetLength
	replacement string
}

type editSet struct {
	edits []edit
}

// NewEditSet returns a new, empty EditSet.
func NewEditSet() EditSet {
	return &editSet{edits: []edit{}}
}

// RelativeToOffset returns a new edit whose offset is the offset of this edit
// minus the given offset, i.e., it is an edit relative to the given offset.
func (e *edit) RelativeToOffset(offset int) edit {
	return edit{
		OffsetLength{
			Offset: e.Offset - offset,
			Length: e.Length,
		},
		e.replacement}
}

func (e *editSet) Add(position OffsetLength, replacement string) error {
	// Check for negative-offset or overlapping edits
	if position.Offset < 0 {
		return fmt.Errorf("edit has negative offset (%d)",
			position.Offset)
	}

	var pos int = len(e.edits)
	for i := len(e.edits) - 1; i >= 0; i-- {
		if e.edits[i].Offset >= position.Offset {
			pos = i
		} else {
			break
		}
	}
	if pos > 0 && e.edits[pos-1].OffsetPastEnd() > position.Offset {
		return fmt.Errorf("overlapping edit at offset %d",
			position.Offset)
	}
	newEdit := edit{position, replacement}
	e.edits = append(e.edits, newEdit)
	copy(e.edits[pos+1:], e.edits[pos:])
	e.edits[pos] = newEdit
	return nil
}

func (e *edit) String() string {
	return "Replace " + e.OffsetLength.String() +
		" with \"" + e.replacement + "\""
}

func (e *editSet) String() string {
	var buffer bytes.Buffer
	for _, edit := range e.edits {
		buffer.WriteString(edit.String())
		buffer.WriteString("\n")
	}
	return buffer.String()
}

func (e *editSet) ApplyTo(in io.Reader, out io.Writer) error {
	bufin := bufio.NewReader(in)
	bufout := bufio.NewWriter(out)
	return e.applyTo(bufin, bufout)
}

func (e *editSet) applyTo(in *bufio.Reader, out *bufio.Writer) error {
	defer out.Flush()
	var offset int = 0
	for _, edit := range e.edits {
		// Copy bytes preceding this edit
		for ; offset < edit.Offset; offset++ {
			byte, err := in.ReadByte()
			if err == io.EOF {
				return fmt.Errorf("edit offset %d is beyond "+
					"the end of the file (%d bytes)",
					edit.Offset, offset)
			} else if err != nil {
				return err
			} else {
				out.WriteByte(byte)
			}
		}
		// Write replacement
		out.WriteString(edit.replacement)
		// Skip bytes replaced by this edit
		for ; offset < (edit.Offset + edit.Length); offset++ {
			_, err := in.ReadByte()
			if err != nil {
				return err
			}
		}
	}
	// Copy remaining bytes until end of file
	for {
		byte, err := in.ReadByte()
		if err == io.EOF {
			return nil
		} else if err != nil {
			return err
		} else {
			out.WriteByte(byte)
		}
	}
	return nil
}

func (e *editSet) CreatePatch(in io.Reader) (result *Patch, err error) {
	return createPatch(e, in)
}

// ApplyToFile reads bytes from a file, applying the edits in an EditSet and
// returning the result as a slice of bytes.
func ApplyToFile(es EditSet, filename string) ([]byte, error) {
	file, err := os.OpenFile(filename, syscall.O_RDWR, 0666)
	if err != nil {
		log.Fatal(err)
	}

	defer file.Close()

	return ApplyToReader(es, file)
}

// ApplyToFile reads bytes from a string, applying the edits in an EditSet and
// returning the result as a string.
func ApplyToString(es EditSet, s string) (string, error) {
	bs, err := ApplyToReader(es, strings.NewReader(s))
	return string(bs), err
}

// ApplyToReader reads bytes from an io.Reader, applying the edits in an
// EditSet and returning the result as a slice of bytes.
func ApplyToReader(es EditSet, in io.Reader) ([]byte, error) {
	var buf bytes.Buffer
	err := es.ApplyTo(in, &buf)
	return buf.Bytes(), err
}
<commit_msg>Revised edit.go per Alan's suggestions<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file defines the EditSet interface and a default implementation,
// including the NewEditSet method.  All Go refactorings/transformations return
// an EditSet, which describes what file(s) will be affected by a refactoring
// and exactly what characters in those files will be affected.

// Contributors: Jeff Overbey, Reed Allman

package doctor

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"syscall"
)

// An EditSet is a collection of changes to be made to a text file.  Each edit
// is comprised of an offset, a length, and a replacement string.
//
// Each edit replaces 0 or more characters at a given offset with a given
// string.  Characters can be inserted by using a position with length 0;
// characters can be deleted by using "" for the replacement string.
//
// Edits are added to an EditSet via the Add method, and the edits in an
// EditSet can be applied to an input by invoking the ApplyTo method or
// one of the utility functions ApplyToString, ApplyToFile, or ApplyToReader.
type EditSet interface {
	// Add inserts an edit into this editSet, returning an error if the
	// edit has a negative offset or overlaps an edit previously added to
	// this EditSet.  If the EditSet is read-only (e.g., Patch implements
	// EditSet but cannot be modified), an error will always be returned.
	Add(position OffsetLength, replacement string) error

	// ApplyTo reads from the given reader, applying the edits in this
	// editSet as it reads, and writes the output to the given writer.
	// It returns an error if there are edits with offsets beyond the end
	// of the input or some other error occurs, such as an I/O error.
	ApplyTo(in io.Reader, out io.Writer) error

	// CreatePatch creates a Patch from this EditSet.  A Patch is itself
	// an EditSet -- it can be applied using ApplyTo -- or it can be output
	// as a unified diff by invoking the Patch's Write method.
	CreatePatch(in io.Reader) (*Patch, error)

	// String returns a human-readable description of this EditSet.
	String() string
}

type edit struct {
	OffsetLength
	replacement string
}

type editSet struct {
	edits []edit
}

// NewEditSet returns a new, empty EditSet.
func NewEditSet() EditSet {
	return &editSet{edits: []edit{}}
}

// RelativeToOffset returns a new edit whose offset is the offset of this edit
// minus the given offset, i.e., it is an edit relative to the given offset.
func (e *edit) RelativeToOffset(offset int) edit {
	return edit{
		OffsetLength{
			Offset: e.Offset - offset,
			Length: e.Length,
		},
		e.replacement}
}

func (e *editSet) Add(position OffsetLength, replacement string) error {
	// Check for negative-offset or overlapping edits
	if position.Offset < 0 {
		return fmt.Errorf("edit has negative offset (%d)",
			position.Offset)
	}

	var pos int = len(e.edits)
	for i := len(e.edits) - 1; i >= 0; i-- {
		if e.edits[i].Offset >= position.Offset {
			pos = i
		} else {
			break
		}
	}
	if pos > 0 && e.edits[pos-1].OffsetPastEnd() > position.Offset {
		return fmt.Errorf("overlapping edit at offset %d",
			position.Offset)
	}
	newEdit := edit{position, replacement}
	e.edits = append(e.edits, newEdit)
	copy(e.edits[pos+1:], e.edits[pos:])
	e.edits[pos] = newEdit
	return nil
}

func (e *edit) String() string {
	return "Replace " + e.OffsetLength.String() +
		" with \"" + e.replacement + "\""
}

func (e *editSet) String() string {
	var buffer bytes.Buffer
	for _, edit := range e.edits {
		buffer.WriteString(edit.String())
		buffer.WriteString("\n")
	}
	return buffer.String()
}

func (e *editSet) ApplyTo(in io.Reader, out io.Writer) error {
	bufin := bufio.NewReader(in)
	bufout := bufio.NewWriter(out)
	return e.applyTo(bufin, bufout)
}

func (e *editSet) applyTo(in *bufio.Reader, out *bufio.Writer) error {
	defer out.Flush()
	var offset int = 0
	for _, edit := range e.edits {
		// Copy bytes preceding this edit
		var bytesToWrite int64 = int64(edit.Offset - offset)
		bytesWritten, err := io.CopyN(out, in, bytesToWrite)
		offset += int(bytesWritten)
		if bytesWritten < bytesToWrite {
			return fmt.Errorf("edit offset %d is beyond "+
				"the end of the file (%d bytes)",
				edit.Offset, offset)
		} else if err != nil {
			return err
		}
		// Write replacement
		out.WriteString(edit.replacement)
		// Skip bytes replaced by this edit
		bytesToWrite = int64((edit.Offset + edit.Length) - offset)
		bytesWritten, err = io.CopyN(ioutil.Discard, in, bytesToWrite)
		offset += int(bytesWritten)
		if bytesWritten < bytesToWrite {
			return fmt.Errorf("edit offset %d is beyond "+
				"the end of the file (%d bytes)",
				edit.Offset, offset)
		} else if err != nil {
			return err
		}
	}
	// Copy remaining bytes until end of file
	_, err := io.Copy(out, in)
	if err != nil {
		return err
	}
	return nil
}

func (e *editSet) CreatePatch(in io.Reader) (result *Patch, err error) {
	return createPatch(e, in)
}

// ApplyToFile reads bytes from a file, applying the edits in an EditSet and
// returning the result as a slice of bytes.
func ApplyToFile(es EditSet, filename string) ([]byte, error) {
	file, err := os.OpenFile(filename, syscall.O_RDWR, 0666)
	if err != nil {
		log.Fatal(err)
	}

	defer file.Close()

	return ApplyToReader(es, file)
}

// ApplyToFile reads bytes from a string, applying the edits in an EditSet and
// returning the result as a string.
func ApplyToString(es EditSet, s string) (string, error) {
	bs, err := ApplyToReader(es, strings.NewReader(s))
	return string(bs), err
}

// ApplyToReader reads bytes from an io.Reader, applying the edits in an
// EditSet and returning the result as a slice of bytes.
func ApplyToReader(es EditSet, in io.Reader) ([]byte, error) {
	var buf bytes.Buffer
	err := es.ApplyTo(in, &buf)
	return buf.Bytes(), err
}
<|endoftext|>


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

// This file defines the EditSet interface and a default implementation,
// including the NewEditSet method.  All Go refactorings/transformations return
// an EditSet, which describes what file(s) will be affected by a refactoring
// and exactly what characters in those files will be affected.

package doctor

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"syscall"
)

// An EditSet is a collection of changes to be made to a text file.  Each edit
// is comprised of an offset, a length, and a replacement string.
//
// Each edit replaces 0 or more characters at a given offset with a given
// string.  Characters can be inserted by using a position with length 0;
// characters can be deleted by using "" for the replacement string.
//
// Edits are added to an EditSet via the Add method, and the edits in an
// EditSet can be applied to an input by invoking the ApplyTo method or
// one of the utility functions ApplyToString, ApplyToFile, or ApplyToReader.
type EditSet struct {
	edits []edit
}

type edit struct {
	OffsetLength
	replacement string
}

// NewEditSet returns a new, empty EditSet.
func NewEditSet() *EditSet {
	return &EditSet{edits: []edit{}}
}

// RelativeToOffset returns a new edit whose offset is the offset of this edit
// minus the given offset, i.e., it is an edit relative to the given offset.
func (e *edit) RelativeToOffset(offset int) edit {
	return edit{
		OffsetLength{
			Offset: e.Offset - offset,
			Length: e.Length,
		},
		e.replacement}
}

// Add inserts an edit into this EditSet, returning an error if the edit has a
// negative offset or overlaps an edit previously added to this EditSet.  If
// the EditSet is read-only (e.g., Patch implements EditSet but cannot be
// modified), an error will always be returned.
func (e *EditSet) Add(position OffsetLength, replacement string) error {
	// Check for negative-offset or overlapping edits
	if position.Offset < 0 {
		return fmt.Errorf("edit has negative offset (%d)",
			position.Offset)
	}

	var pos int = len(e.edits)
	for i := len(e.edits) - 1; i >= 0; i-- {
		if e.edits[i].Offset >= position.Offset {
			pos = i
		} else {
			break
		}
	}
	if pos > 0 && e.edits[pos-1].OffsetPastEnd() > position.Offset {
		return fmt.Errorf("overlapping edit at offset %d",
			position.Offset)
	}
	newEdit := edit{position, replacement}
	e.edits = append(e.edits, newEdit)
	copy(e.edits[pos+1:], e.edits[pos:])
	e.edits[pos] = newEdit
	return nil
}

func (e *edit) String() string {
	return "Replace " + e.OffsetLength.String() +
		" with \"" + e.replacement + "\""
}

// String returns a human-readable description of this EditSet.
func (e *EditSet) String() string {
	var buffer bytes.Buffer
	for _, edit := range e.edits {
		buffer.WriteString(edit.String())
		buffer.WriteString("\n")
	}
	return buffer.String()
}

// ApplyTo reads from the given reader, applying the edits in this EditSet as
// it reads, and writes the output to the given writer.  It returns an error if
// there are edits with offsets beyond the end of the input or some other error
// occurs, such as an I/O error.
func (e *EditSet) ApplyTo(in io.Reader, out io.Writer) error {
	bufin := bufio.NewReader(in)
	bufout := bufio.NewWriter(out)
	return e.applyTo(bufin, bufout)
}

func (e *EditSet) applyTo(in *bufio.Reader, out *bufio.Writer) error {
	defer out.Flush()
	var offset int = 0
	for _, edit := range e.edits {
		// Copy bytes preceding this edit
		var bytesToWrite int64 = int64(edit.Offset - offset)
		bytesWritten, err := io.CopyN(out, in, bytesToWrite)
		offset += int(bytesWritten)
		if bytesWritten < bytesToWrite {
			return fmt.Errorf("edit offset %d is beyond "+
				"the end of the file (%d bytes)",
				edit.Offset, offset)
		} else if err != nil {
			return err
		}
		// Write replacement
		out.WriteString(edit.replacement)
		// Skip bytes replaced by this edit
		bytesToWrite = int64((edit.Offset + edit.Length) - offset)
		bytesWritten, err = io.CopyN(ioutil.Discard, in, bytesToWrite)
		offset += int(bytesWritten)
		if bytesWritten < bytesToWrite {
			return fmt.Errorf("edit offset %d is beyond "+
				"the end of the file (%d bytes)",
				edit.Offset, offset)
		} else if err != nil {
			return err
		}
	}
	// Copy remaining bytes until end of file
	_, err := io.Copy(out, in)
	if err != nil {
		return err
	}
	return nil
}

// CreatePatch creates a Patch from this EditSet.  A Patch can be output as a
// unified diff by invoking the Patch's Write method.
func (e *EditSet) CreatePatch(in io.Reader) (result *Patch, err error) {
	return createPatch(e, in)
}

// CreatePatchForFile reads bytes from a file, applying the edits in an EditSet
// and returning a Patch.
func CreatePatchForFile(es *EditSet, filename string) (*Patch, error) {
	file, err := os.OpenFile(filename, syscall.O_RDWR, 0666)
	if err != nil {
		log.Fatal(err)
	}

	defer file.Close()

	return es.CreatePatch(file)
}

// ApplyToFile reads bytes from a file, applying the edits in an EditSet and
// returning the result as a slice of bytes.
func ApplyToFile(es *EditSet, filename string) ([]byte, error) {
	file, err := os.OpenFile(filename, syscall.O_RDWR, 0666)
	if err != nil {
		log.Fatal(err)
	}

	defer file.Close()

	return ApplyToReader(es, file)
}

// ApplyToFile reads bytes from a string, applying the edits in an EditSet and
// returning the result as a string.
func ApplyToString(es *EditSet, s string) (string, error) {
	bs, err := ApplyToReader(es, strings.NewReader(s))
	return string(bs), err
}

// ApplyToReader reads bytes from an io.Reader, applying the edits in an
// EditSet and returning the result as a slice of bytes.
func ApplyToReader(es *EditSet, in io.Reader) ([]byte, error) {
	var buf bytes.Buffer
	err := es.ApplyTo(in, &buf)
	return buf.Bytes(), err
}
<commit_msg>Updated edit.go comments<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file defines EditSets, which describe changes (additions, deletions,
// and modifications) to be made to a text file.

package doctor

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"syscall"
)

// An EditSet is a collection of changes to be made to a text file.  Each edit
// is comprised of an offset, a length, and a replacement string.
//
// Each edit replaces 0 or more characters at a given offset with a given
// string.  Characters can be inserted by using a position with length 0;
// characters can be deleted by using an empty replacement string.
//
// Edits are added to an EditSet via the Add method, and the edits in an
// EditSet can be applied to an input by invoking the ApplyTo method or
// one of the utility functions ApplyToString, ApplyToFile, or ApplyToReader.
type EditSet struct {
	edits []edit // edits are sorted by offset and are non-overlapping
}

type edit struct {
	OffsetLength
	replacement string
}

// NewEditSet returns a new, empty EditSet.
func NewEditSet() *EditSet {
	return &EditSet{edits: []edit{}}
}

// RelativeToOffset returns a new edit whose offset is the offset of this edit
// minus the given offset, i.e., it is an edit relative to the given offset.
func (e *edit) RelativeToOffset(offset int) edit {
	return edit{
		OffsetLength{
			Offset: e.Offset - offset,
			Length: e.Length,
		},
		e.replacement}
}

// Add inserts an edit into this EditSet, returning an error if the edit has a
// negative offset or overlaps an edit previously added to this EditSet.
func (e *EditSet) Add(position OffsetLength, replacement string) error {
	// Check for negative-offset or overlapping edits
	if position.Offset < 0 {
		return fmt.Errorf("edit has negative offset (%d)",
			position.Offset)
	}

	// Insert edit into e.edits, keeping e.edits sorted by offset
	var pos int = len(e.edits)
	for i := len(e.edits) - 1; i >= 0; i-- {
		if e.edits[i].Offset >= position.Offset {
			pos = i
		} else {
			break
		}
	}
	if pos > 0 && e.edits[pos-1].OffsetPastEnd() > position.Offset {
		return fmt.Errorf("overlapping edit at offset %d",
			position.Offset)
	}
	newEdit := edit{position, replacement}
	e.edits = append(e.edits, newEdit)
	copy(e.edits[pos+1:], e.edits[pos:])
	e.edits[pos] = newEdit
	return nil
}

func (e *edit) String() string {
	return "Replace " + e.OffsetLength.String() +
		" with \"" + e.replacement + "\""
}

// String returns a human-readable description of this EditSet (for debugging).
func (e *EditSet) String() string {
	var buffer bytes.Buffer
	for _, edit := range e.edits {
		buffer.WriteString(edit.String())
		buffer.WriteString("\n")
	}
	return buffer.String()
}

// ApplyTo reads from the given reader, applying the edits in this EditSet as
// it reads, and writes the output to the given writer.  It returns an error if
// there are edits with offsets beyond the end of the input or some other error
// occurs, such as an I/O error.
func (e *EditSet) ApplyTo(in io.Reader, out io.Writer) error {
	bufin := bufio.NewReader(in)
	bufout := bufio.NewWriter(out)
	return e.applyTo(bufin, bufout)
}

func (e *EditSet) applyTo(in *bufio.Reader, out *bufio.Writer) error {
	// This uses the same idea as the linear-time merge in Merge Sort to
	// apply the edits in this EditSet to the bytes from the input reader.
	defer out.Flush()
	var offset int = 0
	for _, edit := range e.edits {
		// Copy bytes preceding this edit
		var bytesToWrite int64 = int64(edit.Offset - offset)
		bytesWritten, err := io.CopyN(out, in, bytesToWrite)
		offset += int(bytesWritten)
		if bytesWritten < bytesToWrite {
			return fmt.Errorf("edit offset %d is beyond "+
				"the end of the file (%d bytes)",
				edit.Offset, offset)
		} else if err != nil {
			return err
		}
		// Write replacement
		out.WriteString(edit.replacement)
		// Skip bytes replaced by this edit
		bytesToWrite = int64((edit.Offset + edit.Length) - offset)
		bytesWritten, err = io.CopyN(ioutil.Discard, in, bytesToWrite)
		offset += int(bytesWritten)
		if bytesWritten < bytesToWrite {
			return fmt.Errorf("edit offset %d is beyond "+
				"the end of the file (%d bytes)",
				edit.Offset, offset)
		} else if err != nil {
			return err
		}
	}
	// Copy remaining bytes until end of file
	_, err := io.Copy(out, in)
	if err != nil {
		return err
	}
	return nil
}

// CreatePatch creates a Patch from this EditSet.  A Patch can be output as a
// unified diff by invoking the Patch's Write method.
func (e *EditSet) CreatePatch(in io.Reader) (result *Patch, err error) {
	return createPatch(e, in)
}

// CreatePatchForFile reads bytes from a file, applying the edits in an EditSet
// and returning a Patch.
func CreatePatchForFile(es *EditSet, filename string) (*Patch, error) {
	file, err := os.OpenFile(filename, syscall.O_RDWR, 0666)
	if err != nil {
		log.Fatal(err)
	}

	defer file.Close()

	return es.CreatePatch(file)
}

// ApplyToFile reads bytes from a file, applying the edits in an EditSet and
// returning the result as a slice of bytes.
func ApplyToFile(es *EditSet, filename string) ([]byte, error) {
	file, err := os.OpenFile(filename, syscall.O_RDWR, 0666)
	if err != nil {
		log.Fatal(err)
	}

	defer file.Close()

	return ApplyToReader(es, file)
}

// ApplyToFile reads bytes from a string, applying the edits in an EditSet and
// returning the result as a string.
func ApplyToString(es *EditSet, s string) (string, error) {
	bs, err := ApplyToReader(es, strings.NewReader(s))
	return string(bs), err
}

// ApplyToReader reads bytes from an io.Reader, applying the edits in an
// EditSet and returning the result as a slice of bytes.
func ApplyToReader(es *EditSet, in io.Reader) ([]byte, error) {
	var buf bytes.Buffer
	err := es.ApplyTo(in, &buf)
	return buf.Bytes(), err
}
<|endoftext|>