<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build js

package ui

import (
	"image"
	"strconv"
	"unicode"

	"github.com/gopherjs/gopherjs/js"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

var canvas *js.Object

type userInterface struct {
	width                int
	height               int
	scale                float64
	fullscreen           bool
	runnableInBackground bool

	deviceScale float64
	sizeChanged bool
	windowFocus bool
}

var currentUI = &userInterface{
	sizeChanged: true,
	windowFocus: true,
}

// NOTE: This returns true even when the browser is not active.
func shown() bool {
	return !js.Global.Get("document").Get("hidden").Bool()
}

func SetScreenSize(width, height int) bool {
	return currentUI.setScreenSize(width, height, currentUI.scale, currentUI.fullscreen)
}

func SetScreenScale(scale float64) bool {
	return currentUI.setScreenSize(currentUI.width, currentUI.height, scale, currentUI.fullscreen)
}

func ScreenScale() float64 {
	return currentUI.scale
}

func SetFullscreen(fullscreen bool) {
	currentUI.setScreenSize(currentUI.width, currentUI.height, currentUI.scale, fullscreen)
}

func IsFullscreen() bool {
	return currentUI.fullscreen
}

func SetRunnableInBackground(runnableInBackground bool) {
	currentUI.runnableInBackground = runnableInBackground
}

func IsRunnableInBackground() bool {
	return currentUI.runnableInBackground
}

func ScreenOffset() (float64, float64) {
	return 0, 0
}

func adjustCursorPosition(x, y int) (int, int) {
	return x, y
}

func IsCursorVisible() bool {
	// The initial value is an empty string, so don't compare with "auto" here.
	return canvas.Get("style").Get("cursor").String() != "none"
}

func SetCursorVisibility(visibility bool) {
	if visibility {
		canvas.Get("style").Set("cursor", "auto")
	} else {
		canvas.Get("style").Set("cursor", "none")
	}
}

func SetWindowIcon(iconImages []image.Image) {
	// Do nothing
}

func (u *userInterface) getScale() float64 {
	if !u.fullscreen {
		return u.scale
	}
	doc := js.Global.Get("document")
	body := doc.Get("body")
	bw := body.Get("clientWidth").Float()
	bh := body.Get("clientHeight").Float()
	sw := bw / float64(u.width)
	sh := bh / float64(u.height)
	if sw > sh {
		return sh
	}
	return sw
}

func (u *userInterface) actualScreenScale() float64 {
	return u.getScale() * u.deviceScale
}

func (u *userInterface) update(g GraphicsContext) error {
	if !u.runnableInBackground && !u.windowFocus {
		return nil
	}
	if opengl.GetContext().IsContextLost() {
		opengl.GetContext().RestoreContext()
		g.Invalidate()
	}
	currentInput.updateGamepads()
	if u.sizeChanged {
		u.sizeChanged = false
		g.SetSize(u.width, u.height, u.actualScreenScale())
		return nil
	}
	if err := g.Update(); err != nil {
		return err
	}
	currentInput.runeBuffer = nil
	return nil
}

func (u *userInterface) loop(g GraphicsContext) error {
	ch := make(chan error)
	var f func()
	f = func() {
		go func() {
			if err := u.update(g); err != nil {
				ch <- err
				close(ch)
				return
			}
			js.Global.Get("window").Call("requestAnimationFrame", f)
		}()
	}
	f()
	return <-ch
}

func touchEventToTouches(e *js.Object) []touch {
	scale := currentUI.getScale()
	j := e.Get("targetTouches")
	rect := canvas.Call("getBoundingClientRect")
	left, top := rect.Get("left").Int(), rect.Get("top").Int()
	t := make([]touch, j.Get("length").Int())
	for i := 0; i < len(t); i++ {
		jj := j.Call("item", i)
		t[i].id = jj.Get("identifier").Int()
		t[i].x = int(float64(jj.Get("clientX").Int()-left) / scale)
		t[i].y = int(float64(jj.Get("clientY").Int()-top) / scale)
	}
	return t
}

func init() {
	if err := initialize(); err != nil {
		panic(err)
	}
}

func initialize() error {
	// Do nothing in node.js.
	if js.Global.Get("require") != js.Undefined {
		return nil
	}

	doc := js.Global.Get("document")
	window := js.Global.Get("window")
	if doc.Get("body") == nil {
		ch := make(chan struct{})
		window.Call("addEventListener", "load", func() {
			close(ch)
		})
		<-ch
	}
	window.Call("addEventListener", "focus", func() {
		currentUI.windowFocus = true
	})
	window.Call("addEventListener", "blur", func() {
		currentUI.windowFocus = false
	})
	window.Call("addEventListener", "resize", func() {
		currentUI.updateScreenSize()
	})

	// Adjust the initial scale to 1.
	// https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
	meta := doc.Call("createElement", "meta")
	meta.Set("name", "viewport")
	meta.Set("content", "width=device-width, initial-scale=1")
	doc.Get("head").Call("appendChild", meta)

	canvas = doc.Call("createElement", "canvas")
	canvas.Set("width", 16)
	canvas.Set("height", 16)
	doc.Get("body").Call("appendChild", canvas)

	htmlStyle := doc.Get("documentElement").Get("style")
	htmlStyle.Set("height", "100%")
	htmlStyle.Set("margin", "0")
	htmlStyle.Set("padding", "0")

	bodyStyle := doc.Get("body").Get("style")
	bodyStyle.Set("backgroundColor", "#000")
	bodyStyle.Set("position", "relative")
	bodyStyle.Set("height", "100%")
	bodyStyle.Set("margin", "0")
	bodyStyle.Set("padding", "0")
	// TODO: This is OK as long as the game is in an independent iframe.
	// What if the canvas is embedded in a HTML directly?
	doc.Get("body").Call("addEventListener", "click", func() {
		canvas.Call("focus")
	})

	canvasStyle := canvas.Get("style")
	canvasStyle.Set("position", "absolute")

	// Make the canvas focusable.
	canvas.Call("setAttribute", "tabindex", 1)
	canvas.Get("style").Set("outline", "none")

	// Keyboard
	canvas.Call("addEventListener", "keydown", func(e *js.Object) {
		if e.Get("code") == js.Undefined {
			// Assume that UA is Safari.
			code := e.Get("keyCode").Int()
			currentInput.keyDownSafari(code)
			return
		}
		code := e.Get("code").String()
		currentInput.keyDown(code)
	})
	canvas.Call("addEventListener", "keypress", func(e *js.Object) {
		e.Call("preventDefault")
		if r := rune(e.Get("charCode").Int()); unicode.IsPrint(r) {
			currentInput.runeBuffer = append(currentInput.runeBuffer, r)
		}
	})
	canvas.Call("addEventListener", "keyup", func(e *js.Object) {
		e.Call("preventDefault")
		if e.Get("code") == js.Undefined {
			// Assume that UA is Safari.
			code := e.Get("keyCode").Int()
			currentInput.keyUpSafari(code)
		}
		code := e.Get("code").String()
		currentInput.keyUp(code)
	})

	// Mouse
	canvas.Call("addEventListener", "mousedown", func(e *js.Object) {
		e.Call("preventDefault")
		button := e.Get("button").Int()
		currentInput.mouseDown(button)
		setMouseCursorFromEvent(e)
	})
	canvas.Call("addEventListener", "mouseup", func(e *js.Object) {
		e.Call("preventDefault")
		button := e.Get("button").Int()
		currentInput.mouseUp(button)
		setMouseCursorFromEvent(e)
	})
	canvas.Call("addEventListener", "mousemove", func(e *js.Object) {
		e.Call("preventDefault")
		setMouseCursorFromEvent(e)
	})
	canvas.Call("addEventListener", "contextmenu", func(e *js.Object) {
		e.Call("preventDefault")
	})

	// Touch
	canvas.Call("addEventListener", "touchstart", func(e *js.Object) {
		e.Call("preventDefault")
		currentInput.updateTouches(touchEventToTouches(e))
	})
	canvas.Call("addEventListener", "touchend", func(e *js.Object) {
		e.Call("preventDefault")
		currentInput.updateTouches(touchEventToTouches(e))
	})
	canvas.Call("addEventListener", "touchmove", func(e *js.Object) {
		e.Call("preventDefault")
		currentInput.updateTouches(touchEventToTouches(e))
	})

	// Gamepad
	window.Call("addEventListener", "gamepadconnected", func(e *js.Object) {
		// Do nothing.
	})

	canvas.Call("addEventListener", "webglcontextlost", func(e *js.Object) {
		e.Call("preventDefault")
	})
	canvas.Call("addEventListener", "webglcontextrestored", func(e *js.Object) {
		// Do nothing.
	})

	return nil
}

func setMouseCursorFromEvent(e *js.Object) {
	scale := currentUI.getScale()
	rect := canvas.Call("getBoundingClientRect")
	x, y := e.Get("clientX").Int(), e.Get("clientY").Int()
	x -= rect.Get("left").Int()
	y -= rect.Get("top").Int()
	currentInput.setMouseCursor(int(float64(x)/scale), int(float64(y)/scale))
}

func devicePixelRatio() float64 {
	ratio := js.Global.Get("window").Get("devicePixelRatio").Float()
	if ratio == 0 {
		ratio = 1
	}
	return ratio
}

func RunMainThreadLoop(ch <-chan error) error {
	return <-ch
}

func Run(width, height int, scale float64, title string, g GraphicsContext) error {
	u := currentUI
	doc := js.Global.Get("document")
	doc.Set("title", title)
	u.setScreenSize(width, height, scale, u.fullscreen)
	canvas.Call("focus")
	if err := opengl.Init(); err != nil {
		return err
	}
	return u.loop(g)
}

func (u *userInterface) setScreenSize(width, height int, scale float64, fullscreen bool) bool {
	if u.width == width && u.height == height &&
		u.scale == scale && fullscreen == u.fullscreen {
		return false
	}
	u.width = width
	u.height = height
	u.scale = scale
	u.fullscreen = fullscreen
	u.updateScreenSize()
	return true
}

func (u *userInterface) updateScreenSize() {
	// CSS imageRendering seems useful to enlarge the screen,
	// but doesn't work in some cases (#306):
	// * Chrome just after restoring the lost context
	// * Safari
	// Let's use the pixel ratio as it is here.
	u.deviceScale = devicePixelRatio()

	canvas.Set("width", int(float64(u.width)*u.actualScreenScale()))
	canvas.Set("height", int(float64(u.height)*u.actualScreenScale()))
	canvasStyle := canvas.Get("style")

	s := u.getScale()
	cssWidth := int(float64(u.width) * s)
	cssHeight := int(float64(u.height) * s)
	canvasStyle.Set("width", strconv.Itoa(cssWidth)+"px")
	canvasStyle.Set("height", strconv.Itoa(cssHeight)+"px")
	// CSS calc requires space chars.
	canvasStyle.Set("left", "calc((100% - "+strconv.Itoa(cssWidth)+"px) / 2)")
	canvasStyle.Set("top", "calc((100% - "+strconv.Itoa(cssHeight)+"px) / 2)")

	u.sizeChanged = true
}
<commit_msg>ui: Bug fix: call preventDefault for arrow keys<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build js

package ui

import (
	"image"
	"strconv"
	"unicode"

	"github.com/gopherjs/gopherjs/js"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

var canvas *js.Object

type userInterface struct {
	width                int
	height               int
	scale                float64
	fullscreen           bool
	runnableInBackground bool

	deviceScale float64
	sizeChanged bool
	windowFocus bool
}

var currentUI = &userInterface{
	sizeChanged: true,
	windowFocus: true,
}

// NOTE: This returns true even when the browser is not active.
func shown() bool {
	return !js.Global.Get("document").Get("hidden").Bool()
}

func SetScreenSize(width, height int) bool {
	return currentUI.setScreenSize(width, height, currentUI.scale, currentUI.fullscreen)
}

func SetScreenScale(scale float64) bool {
	return currentUI.setScreenSize(currentUI.width, currentUI.height, scale, currentUI.fullscreen)
}

func ScreenScale() float64 {
	return currentUI.scale
}

func SetFullscreen(fullscreen bool) {
	currentUI.setScreenSize(currentUI.width, currentUI.height, currentUI.scale, fullscreen)
}

func IsFullscreen() bool {
	return currentUI.fullscreen
}

func SetRunnableInBackground(runnableInBackground bool) {
	currentUI.runnableInBackground = runnableInBackground
}

func IsRunnableInBackground() bool {
	return currentUI.runnableInBackground
}

func ScreenOffset() (float64, float64) {
	return 0, 0
}

func adjustCursorPosition(x, y int) (int, int) {
	return x, y
}

func IsCursorVisible() bool {
	// The initial value is an empty string, so don't compare with "auto" here.
	return canvas.Get("style").Get("cursor").String() != "none"
}

func SetCursorVisibility(visibility bool) {
	if visibility {
		canvas.Get("style").Set("cursor", "auto")
	} else {
		canvas.Get("style").Set("cursor", "none")
	}
}

func SetWindowIcon(iconImages []image.Image) {
	// Do nothing
}

func (u *userInterface) getScale() float64 {
	if !u.fullscreen {
		return u.scale
	}
	doc := js.Global.Get("document")
	body := doc.Get("body")
	bw := body.Get("clientWidth").Float()
	bh := body.Get("clientHeight").Float()
	sw := bw / float64(u.width)
	sh := bh / float64(u.height)
	if sw > sh {
		return sh
	}
	return sw
}

func (u *userInterface) actualScreenScale() float64 {
	return u.getScale() * u.deviceScale
}

func (u *userInterface) update(g GraphicsContext) error {
	if !u.runnableInBackground && !u.windowFocus {
		return nil
	}
	if opengl.GetContext().IsContextLost() {
		opengl.GetContext().RestoreContext()
		g.Invalidate()
	}
	currentInput.updateGamepads()
	if u.sizeChanged {
		u.sizeChanged = false
		g.SetSize(u.width, u.height, u.actualScreenScale())
		return nil
	}
	if err := g.Update(); err != nil {
		return err
	}
	currentInput.runeBuffer = nil
	return nil
}

func (u *userInterface) loop(g GraphicsContext) error {
	ch := make(chan error)
	var f func()
	f = func() {
		go func() {
			if err := u.update(g); err != nil {
				ch <- err
				close(ch)
				return
			}
			js.Global.Get("window").Call("requestAnimationFrame", f)
		}()
	}
	f()
	return <-ch
}

func touchEventToTouches(e *js.Object) []touch {
	scale := currentUI.getScale()
	j := e.Get("targetTouches")
	rect := canvas.Call("getBoundingClientRect")
	left, top := rect.Get("left").Int(), rect.Get("top").Int()
	t := make([]touch, j.Get("length").Int())
	for i := 0; i < len(t); i++ {
		jj := j.Call("item", i)
		t[i].id = jj.Get("identifier").Int()
		t[i].x = int(float64(jj.Get("clientX").Int()-left) / scale)
		t[i].y = int(float64(jj.Get("clientY").Int()-top) / scale)
	}
	return t
}

func init() {
	if err := initialize(); err != nil {
		panic(err)
	}
}

func initialize() error {
	// Do nothing in node.js.
	if js.Global.Get("require") != js.Undefined {
		return nil
	}

	doc := js.Global.Get("document")
	window := js.Global.Get("window")
	if doc.Get("body") == nil {
		ch := make(chan struct{})
		window.Call("addEventListener", "load", func() {
			close(ch)
		})
		<-ch
	}
	window.Call("addEventListener", "focus", func() {
		currentUI.windowFocus = true
	})
	window.Call("addEventListener", "blur", func() {
		currentUI.windowFocus = false
	})
	window.Call("addEventListener", "resize", func() {
		currentUI.updateScreenSize()
	})

	// Adjust the initial scale to 1.
	// https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
	meta := doc.Call("createElement", "meta")
	meta.Set("name", "viewport")
	meta.Set("content", "width=device-width, initial-scale=1")
	doc.Get("head").Call("appendChild", meta)

	canvas = doc.Call("createElement", "canvas")
	canvas.Set("width", 16)
	canvas.Set("height", 16)
	doc.Get("body").Call("appendChild", canvas)

	htmlStyle := doc.Get("documentElement").Get("style")
	htmlStyle.Set("height", "100%")
	htmlStyle.Set("margin", "0")
	htmlStyle.Set("padding", "0")

	bodyStyle := doc.Get("body").Get("style")
	bodyStyle.Set("backgroundColor", "#000")
	bodyStyle.Set("position", "relative")
	bodyStyle.Set("height", "100%")
	bodyStyle.Set("margin", "0")
	bodyStyle.Set("padding", "0")
	// TODO: This is OK as long as the game is in an independent iframe.
	// What if the canvas is embedded in a HTML directly?
	doc.Get("body").Call("addEventListener", "click", func() {
		canvas.Call("focus")
	})

	canvasStyle := canvas.Get("style")
	canvasStyle.Set("position", "absolute")

	// Make the canvas focusable.
	canvas.Call("setAttribute", "tabindex", 1)
	canvas.Get("style").Set("outline", "none")

	// Keyboard
	canvas.Call("addEventListener", "keydown", func(e *js.Object) {
		c := e.Get("code")
		if c == js.Undefined {
			// TODO: Now this is used for Edge, not Safari. Rename functions.
			code := e.Get("keyCode").Int()
			if keyCodeToKeySafari[code] == KeyUp ||
				keyCodeToKeySafari[code] == KeyDown ||
				keyCodeToKeySafari[code] == KeyLeft ||
				keyCodeToKeySafari[code] == KeyRight {
				currentInput.keyDownSafari(code)
			}
			return
		}
		cs := c.String()
		if cs == keyToCodes[KeyUp][0] ||
			cs == keyToCodes[KeyDown][0] ||
			cs == keyToCodes[KeyLeft][0] ||
			cs == keyToCodes[KeyRight][0] {
			e.Call("preventDefault")
		}
		currentInput.keyDown(cs)
	})
	canvas.Call("addEventListener", "keypress", func(e *js.Object) {
		e.Call("preventDefault")
		if r := rune(e.Get("charCode").Int()); unicode.IsPrint(r) {
			currentInput.runeBuffer = append(currentInput.runeBuffer, r)
		}
	})
	canvas.Call("addEventListener", "keyup", func(e *js.Object) {
		e.Call("preventDefault")
		if e.Get("code") == js.Undefined {
			// Assume that UA is Safari.
			code := e.Get("keyCode").Int()
			currentInput.keyUpSafari(code)
		}
		code := e.Get("code").String()
		currentInput.keyUp(code)
	})

	// Mouse
	canvas.Call("addEventListener", "mousedown", func(e *js.Object) {
		e.Call("preventDefault")
		button := e.Get("button").Int()
		currentInput.mouseDown(button)
		setMouseCursorFromEvent(e)
	})
	canvas.Call("addEventListener", "mouseup", func(e *js.Object) {
		e.Call("preventDefault")
		button := e.Get("button").Int()
		currentInput.mouseUp(button)
		setMouseCursorFromEvent(e)
	})
	canvas.Call("addEventListener", "mousemove", func(e *js.Object) {
		e.Call("preventDefault")
		setMouseCursorFromEvent(e)
	})
	canvas.Call("addEventListener", "contextmenu", func(e *js.Object) {
		e.Call("preventDefault")
	})

	// Touch
	canvas.Call("addEventListener", "touchstart", func(e *js.Object) {
		e.Call("preventDefault")
		currentInput.updateTouches(touchEventToTouches(e))
	})
	canvas.Call("addEventListener", "touchend", func(e *js.Object) {
		e.Call("preventDefault")
		currentInput.updateTouches(touchEventToTouches(e))
	})
	canvas.Call("addEventListener", "touchmove", func(e *js.Object) {
		e.Call("preventDefault")
		currentInput.updateTouches(touchEventToTouches(e))
	})

	// Gamepad
	window.Call("addEventListener", "gamepadconnected", func(e *js.Object) {
		// Do nothing.
	})

	canvas.Call("addEventListener", "webglcontextlost", func(e *js.Object) {
		e.Call("preventDefault")
	})
	canvas.Call("addEventListener", "webglcontextrestored", func(e *js.Object) {
		// Do nothing.
	})

	return nil
}

func setMouseCursorFromEvent(e *js.Object) {
	scale := currentUI.getScale()
	rect := canvas.Call("getBoundingClientRect")
	x, y := e.Get("clientX").Int(), e.Get("clientY").Int()
	x -= rect.Get("left").Int()
	y -= rect.Get("top").Int()
	currentInput.setMouseCursor(int(float64(x)/scale), int(float64(y)/scale))
}

func devicePixelRatio() float64 {
	ratio := js.Global.Get("window").Get("devicePixelRatio").Float()
	if ratio == 0 {
		ratio = 1
	}
	return ratio
}

func RunMainThreadLoop(ch <-chan error) error {
	return <-ch
}

func Run(width, height int, scale float64, title string, g GraphicsContext) error {
	u := currentUI
	doc := js.Global.Get("document")
	doc.Set("title", title)
	u.setScreenSize(width, height, scale, u.fullscreen)
	canvas.Call("focus")
	if err := opengl.Init(); err != nil {
		return err
	}
	return u.loop(g)
}

func (u *userInterface) setScreenSize(width, height int, scale float64, fullscreen bool) bool {
	if u.width == width && u.height == height &&
		u.scale == scale && fullscreen == u.fullscreen {
		return false
	}
	u.width = width
	u.height = height
	u.scale = scale
	u.fullscreen = fullscreen
	u.updateScreenSize()
	return true
}

func (u *userInterface) updateScreenSize() {
	// CSS imageRendering seems useful to enlarge the screen,
	// but doesn't work in some cases (#306):
	// * Chrome just after restoring the lost context
	// * Safari
	// Let's use the pixel ratio as it is here.
	u.deviceScale = devicePixelRatio()

	canvas.Set("width", int(float64(u.width)*u.actualScreenScale()))
	canvas.Set("height", int(float64(u.height)*u.actualScreenScale()))
	canvasStyle := canvas.Get("style")

	s := u.getScale()
	cssWidth := int(float64(u.width) * s)
	cssHeight := int(float64(u.height) * s)
	canvasStyle.Set("width", strconv.Itoa(cssWidth)+"px")
	canvasStyle.Set("height", strconv.Itoa(cssHeight)+"px")
	// CSS calc requires space chars.
	canvasStyle.Set("left", "calc((100% - "+strconv.Itoa(cssWidth)+"px) / 2)")
	canvasStyle.Set("top", "calc((100% - "+strconv.Itoa(cssHeight)+"px) / 2)")

	u.sizeChanged = true
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Che Wei, Lin
//
// 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 tinynet

import (
	"github.com/stretchr/testify/assert"
	"sync"
	"testing"
)

var ovsSwitch *OVSSwitch
var l sync.Mutex
var bridgeName string = "tinynet"

func TestNewOVSSwitch(t *testing.T) {
	var err error
	l.Lock()
	ovsSwitch, err = NewOVSSwitch(bridgeName)
	assert.NoError(t, err)
	l.Unlock()
}

func TestDeleteOVSSwitch(t *testing.T) {
	l.Lock()
	err := ovsSwitch.Delete()
	assert.NoError(t, err)
	l.Unlock()
}

func TestNewOVSSwitch_Invalid(t *testing.T) {
	_, err := NewOVSSwitch("")
	assert.Error(t, err)
}

func TestDeleteOVSSwitch_Invalid(t *testing.T) {
	l.Lock()
	err := ovsSwitch.Delete()
	assert.Error(t, err)
	l.Unlock()
}
<commit_msg>remove lock<commit_after>// Copyright (c) 2017 Che Wei, Lin
//
// 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 tinynet

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

var ovsSwitch *OVSSwitch
var bridgeName string = "tinynet"

func TestNewOVSSwitch(t *testing.T) {
	var err error
	ovsSwitch, err = NewOVSSwitch(bridgeName)
	assert.NoError(t, err)
}

func TestDeleteOVSSwitch(t *testing.T) {
	err := ovsSwitch.Delete()
	assert.NoError(t, err)
}

func TestNewOVSSwitch_Invalid(t *testing.T) {
	_, err := NewOVSSwitch("")
	assert.Error(t, err)
}

func TestDeleteOVSSwitch_Invalid(t *testing.T) {
	err := ovsSwitch.Delete()
	assert.Error(t, err)
}
<|endoftext|>


<commit_before>package fs

import (
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"time"

	"github.com/kopia/kopia/cas"
)

const (
	maxDirectoryEntrySize = 65000
)

var (
	invalidDirectoryDataError = errors.New("invalid directory data")
)

type directoryEntry struct {
	name     string
	fileMode uint32
	fileSize int64
	modTime  time.Time
	userID   uint32
	groupID  uint32
	objectID string
}

type jsonDirectoryEntry struct {
	FileName    string    `json:"f,omitempty"`
	DirName     string    `json:"d,omitempty"`
	Permissions string    `json:"p,omitempty"`
	Size        string    `json:"s,omitempty"`
	Time        time.Time `json:"t"`
	Owner       string    `json:"o,omitempty"`
	ObjectID    string    `json:"oid,omitempty"`
}

func (de *directoryEntry) Name() string {
	return de.name
}

func (de *directoryEntry) Mode() os.FileMode {
	return os.FileMode(de.fileMode)
}

func (de *directoryEntry) IsDir() bool {
	return de.Mode().IsDir()
}

func (de *directoryEntry) Size() int64 {
	if de.Mode().IsRegular() {
		return de.fileSize
	}

	return 0
}

func (de *directoryEntry) UserID() uint32 {
	return de.userID
}

func (de *directoryEntry) GroupID() uint32 {
	return de.groupID
}

func (de *directoryEntry) ModTime() time.Time {
	return de.modTime
}

func (de *directoryEntry) ObjectID() cas.ObjectID {
	return cas.ObjectID(de.objectID)
}

func (de *directoryEntry) Sys() interface{} {
	return nil
}

func (de *directoryEntry) fromJSON(jde *jsonDirectoryEntry) error {
	var mode uint32

	switch {
	case jde.DirName != "":
		de.name = jde.DirName
		mode = uint32(os.ModeDir)

	case jde.FileName != "":
		de.name = jde.FileName
		mode = 0
	}

	if jde.Permissions != "" {
		s, err := strconv.ParseUint(jde.Permissions, 8, 32)
		if err != nil {
			return err
		}
		mode |= uint32(s)
	}

	de.fileMode = mode
	de.modTime = jde.Time
	if jde.Owner != "" {
		fmt.Sscanf(jde.Owner, "%d:%d", &de.userID, &de.groupID)
	}
	de.objectID = jde.ObjectID

	if jde.Size != "" {
		s, err := strconv.ParseInt(jde.Size, 10, 64)
		if err != nil {
			return err
		}
		de.fileSize = s
	}
	return nil
}

type directoryWriter struct {
	io.Closer

	writer    io.Writer
	buf       []byte
	separator []byte
}

func (dw *directoryWriter) WriteEntry(e Entry) error {
	var jde jsonDirectoryEntry

	m := e.Mode()
	switch m & os.ModeType {
	case os.ModeDir:
		jde.DirName = e.Name()
	default:
		jde.FileName = e.Name()
		jde.Size = strconv.FormatInt(e.Size(), 10)
	}

	jde.Permissions = strconv.FormatInt(int64(e.Mode()&os.ModePerm), 8)
	jde.Time = e.ModTime()
	jde.Owner = fmt.Sprintf("%d:%d", e.UserID(), e.GroupID())
	jde.ObjectID = string(e.ObjectID())

	v, _ := json.Marshal(&jde)

	dw.writer.Write(dw.separator)
	dw.writer.Write(v)
	dw.separator = []byte(",\n  ")

	return nil
}

func (dw *directoryWriter) Close() error {
	dw.writer.Write([]byte("\n]}\n"))
	return nil
}

func (*directoryWriter) serializeLengthPrefixedString(buf []byte, s string) int {
	offset := binary.PutUvarint(buf, uint64(len(s)))
	copy(buf[offset:], s)
	offset += len(s)
	return offset
}

func newDirectoryWriter(w io.Writer) *directoryWriter {
	dw := &directoryWriter{
		writer: w,
	}

	var f directoryFormat
	f.Version = 1

	io.WriteString(w, "{\n\"format\":")
	b, _ := json.Marshal(&f)
	w.Write(b)
	io.WriteString(w, ",\n\"entries\":[")
	dw.separator = []byte("\n  ")

	return dw
}

type directoryReader struct {
	reader  io.Reader
	decoder *json.Decoder
}

func (dr *directoryReader) ReadNext() (Entry, error) {
	if dr.decoder.More() {
		var jde jsonDirectoryEntry
		if err := dr.decoder.Decode(&jde); err != nil {
			return nil, err
		}

		var de directoryEntry
		if err := de.fromJSON(&jde); err != nil {
			return nil, err
		}

		return &de, nil
	}
	return nil, io.EOF
}

type directoryFormat struct {
	Version int `json:"version"`
}

func newDirectoryReader(r io.Reader) (*directoryReader, error) {
	dr := &directoryReader{
		decoder: json.NewDecoder(r),
	}

	var t json.Token
	var err error

	// Expect opening '{'
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if t != json.Delim('{') {
		return nil, fmt.Errorf("invalid directory data: expected '{', got %v", t)
	}

	// Expect "format"
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if s, ok := t.(string); ok {
		if s != "format" {
			return nil, fmt.Errorf("invalid directory data: expected 'format', got '%v'", s)
		}
	} else {
		return nil, fmt.Errorf("invalid directory data: expected 'format', got '%v'", t)
	}

	// Parse format and trailing comma
	var format directoryFormat
	err = dr.decoder.Decode(&format)
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	// Expect "entries"
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if s, ok := t.(string); ok {
		if s != "entries" {
			return nil, fmt.Errorf("invalid directory data: expected 'entries', got '%v'", s)
		}
	} else {
		return nil, fmt.Errorf("invalid directory data: expected 'entries', got '%v'", t)
	}

	// Expect opening '['
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if t != json.Delim('[') {
		return nil, fmt.Errorf("invalid directory data: expected '[', got %v", t)
	}

	return dr, nil
}

func ReadDirectory(r io.Reader, namePrefix string) (Directory, error) {
	dr, err := newDirectoryReader(r)
	if err != nil {
		return nil, err
	}

	var dir Directory
	for {
		e, err := dr.ReadNext()
		if e != nil {
			dir = append(dir, e)
		}
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
	}

	return dir, nil
}
<commit_msg>dir parser<commit_after>package fs

import (
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"time"

	"github.com/kopia/kopia/cas"
)

const (
	maxDirectoryEntrySize = 65000
)

var (
	invalidDirectoryDataError = errors.New("invalid directory data")
)

type directoryEntry struct {
	name     string
	fileMode uint32
	fileSize int64
	modTime  time.Time
	userID   uint32
	groupID  uint32
	objectID string
}

type jsonDirectoryEntry struct {
	FileName    string    `json:"f,omitempty"`
	DirName     string    `json:"d,omitempty"`
	Permissions string    `json:"p,omitempty"`
	Size        string    `json:"s,omitempty"`
	Time        time.Time `json:"t"`
	Owner       string    `json:"o,omitempty"`
	ObjectID    string    `json:"oid,omitempty"`
}

func (de *directoryEntry) Name() string {
	return de.name
}

func (de *directoryEntry) Mode() os.FileMode {
	return os.FileMode(de.fileMode)
}

func (de *directoryEntry) IsDir() bool {
	return de.Mode().IsDir()
}

func (de *directoryEntry) Size() int64 {
	if de.Mode().IsRegular() {
		return de.fileSize
	}

	return 0
}

func (de *directoryEntry) UserID() uint32 {
	return de.userID
}

func (de *directoryEntry) GroupID() uint32 {
	return de.groupID
}

func (de *directoryEntry) ModTime() time.Time {
	return de.modTime
}

func (de *directoryEntry) ObjectID() cas.ObjectID {
	return cas.ObjectID(de.objectID)
}

func (de *directoryEntry) Sys() interface{} {
	return nil
}

func (de *directoryEntry) fromJSON(jde *jsonDirectoryEntry) error {
	var mode uint32

	switch {
	case jde.DirName != "":
		de.name = jde.DirName
		mode = uint32(os.ModeDir)

	case jde.FileName != "":
		de.name = jde.FileName
		mode = 0
	}

	if jde.Permissions != "" {
		s, err := strconv.ParseUint(jde.Permissions, 8, 32)
		if err != nil {
			return err
		}
		mode |= uint32(s)
	}

	de.fileMode = mode
	de.modTime = jde.Time
	if jde.Owner != "" {
		fmt.Sscanf(jde.Owner, "%d:%d", &de.userID, &de.groupID)
	}
	de.objectID = jde.ObjectID

	if jde.Size != "" {
		s, err := strconv.ParseInt(jde.Size, 10, 64)
		if err != nil {
			return err
		}
		de.fileSize = s
	}
	return nil
}

type directoryWriter struct {
	io.Closer

	writer    io.Writer
	buf       []byte
	separator []byte
}

func (dw *directoryWriter) WriteEntry(e Entry) error {
	var jde jsonDirectoryEntry

	m := e.Mode()
	switch m & os.ModeType {
	case os.ModeDir:
		jde.DirName = e.Name()
	default:
		jde.FileName = e.Name()
		jde.Size = strconv.FormatInt(e.Size(), 10)
	}

	jde.Permissions = strconv.FormatInt(int64(e.Mode()&os.ModePerm), 8)
	jde.Time = e.ModTime()
	jde.Owner = fmt.Sprintf("%d:%d", e.UserID(), e.GroupID())
	jde.ObjectID = string(e.ObjectID())

	v, _ := json.Marshal(&jde)

	dw.writer.Write(dw.separator)
	dw.writer.Write(v)
	dw.separator = []byte(",\n  ")

	return nil
}

func (dw *directoryWriter) Close() error {
	dw.writer.Write([]byte("\n]}\n"))
	return nil
}

func (*directoryWriter) serializeLengthPrefixedString(buf []byte, s string) int {
	offset := binary.PutUvarint(buf, uint64(len(s)))
	copy(buf[offset:], s)
	offset += len(s)
	return offset
}

func newDirectoryWriter(w io.Writer) *directoryWriter {
	dw := &directoryWriter{
		writer: w,
	}

	var f directoryFormat
	f.Version = 1

	io.WriteString(w, "{\n\"format\":")
	b, _ := json.Marshal(&f)
	w.Write(b)
	io.WriteString(w, ",\n\"entries\":[")
	dw.separator = []byte("\n  ")

	return dw
}

type directoryReader struct {
	reader  io.Reader
	decoder *json.Decoder
}

func (dr *directoryReader) ReadNext() (Entry, error) {
	if dr.decoder.More() {
		var jde jsonDirectoryEntry
		if err := dr.decoder.Decode(&jde); err != nil {
			return nil, err
		}

		var de directoryEntry
		if err := de.fromJSON(&jde); err != nil {
			return nil, err
		}

		return &de, nil
	}

	// Expect ']'
	t, err := dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if t != json.Delim(']') {
		return nil, fmt.Errorf("invalid directory data: expected ']', got %v", t)
	}

	// Expect '}'
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if t != json.Delim('}') {
		return nil, fmt.Errorf("invalid directory data: expected ']', got %v", t)
	}

	// Expect end of file
	t, err = dr.decoder.Token()
	if err != io.EOF {
		return nil, fmt.Errorf("invalid directory data: expected EOF, got %v", t)
	}

	return nil, io.EOF
}

type directoryFormat struct {
	Version int `json:"version"`
}

func newDirectoryReader(r io.Reader) (*directoryReader, error) {
	dr := &directoryReader{
		decoder: json.NewDecoder(r),
	}

	var t json.Token
	var err error

	// Expect opening '{'
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if t != json.Delim('{') {
		return nil, fmt.Errorf("invalid directory data: expected '{', got %v", t)
	}

	// Expect "format"
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if s, ok := t.(string); ok {
		if s != "format" {
			return nil, fmt.Errorf("invalid directory data: expected 'format', got '%v'", s)
		}
	} else {
		return nil, fmt.Errorf("invalid directory data: expected 'format', got '%v'", t)
	}

	// Parse format and trailing comma
	var format directoryFormat
	err = dr.decoder.Decode(&format)
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	// Expect "entries"
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if s, ok := t.(string); ok {
		if s != "entries" {
			return nil, fmt.Errorf("invalid directory data: expected 'entries', got '%v'", s)
		}
	} else {
		return nil, fmt.Errorf("invalid directory data: expected 'entries', got '%v'", t)
	}

	// Expect opening '['
	t, err = dr.decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("invalid directory data: %v", err)
	}

	if t != json.Delim('[') {
		return nil, fmt.Errorf("invalid directory data: expected '[', got %v", t)
	}

	return dr, nil
}

func ReadDirectory(r io.Reader, namePrefix string) (Directory, error) {
	dr, err := newDirectoryReader(r)
	if err != nil {
		return nil, err
	}

	var dir Directory
	for {
		e, err := dr.ReadNext()
		if e != nil {
			dir = append(dir, e)
		}
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
	}

	return dir, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/fsouza/go-dockerclient"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"text/template"
)

var (
	watch       bool
	notifyCmd   string
	onlyExposed bool
	configFile  string
	configs     ConfigFile
)

type Event struct {
	ContainerId string `json:"id"`
	Status      string `json:"status"`
	Image       string `json:"from"`
}

type Address struct {
	IP   string
	Port string
}
type RuntimeContainer struct {
	ID        string
	Addresses []Address
	Image     string
	Env       map[string]string
}

type Config struct {
	Template    string
	Dest        string
	Watch       bool
	NotifyCmd   string
	OnlyExposed bool
}

type ConfigFile struct {
	Config []Config
}

func (c *ConfigFile) filterWatches() ConfigFile {
	configWithWatches := []Config{}

	for _, config := range c.Config {
		if config.Watch {
			configWithWatches = append(configWithWatches, config)
		}
	}
	return ConfigFile{
		Config: configWithWatches,
	}
}

func deepGet(item interface{}, path string) interface{} {
	if path == "" {
		return item
	}

	parts := strings.Split(path, ".")
	itemValue := reflect.ValueOf(item)

	if len(parts) > 0 {
		switch itemValue.Kind() {
		case reflect.Struct:
			fieldValue := itemValue.FieldByName(parts[0])
			if fieldValue.IsValid() {
				return deepGet(fieldValue.Interface(), strings.Join(parts[1:], "."))
			}
		case reflect.Map:
			mapValue := itemValue.MapIndex(reflect.ValueOf(parts[0]))
			if mapValue.IsValid() {
				return deepGet(mapValue.Interface(), strings.Join(parts[1:], "."))
			}
		default:
			fmt.Printf("can't group by %s\n", path)
		}
		return nil
	}

	return itemValue.Interface()
}

func groupBy(entries []*RuntimeContainer, key string) map[string][]*RuntimeContainer {
	groups := make(map[string][]*RuntimeContainer)
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			groups[value.(string)] = append(groups[value.(string)], v)
		}
	}
	return groups
}

func contains(a map[string]string, b string) bool {
	if _, ok := a[b]; ok {
		return true
	}
	return false
}

func usage() {
	println("Usage: docker-gen [-config file] [-watch=false] [-notify=\"restart xyz\"] <template> [<dest>]")
}

func generateFile(config Config, containers []*RuntimeContainer) {
	templatePath := config.Template
	tmpl, err := template.New(filepath.Base(templatePath)).Funcs(template.FuncMap{
		"contains": contains,
		"groupBy":  groupBy,
	}).ParseFiles(templatePath)
	if err != nil {
		panic(err)
	}

	filteredContainers := []*RuntimeContainer{}
	if config.OnlyExposed {
		for _, container := range containers {
			if len(container.Addresses) > 0 {
				filteredContainers = append(filteredContainers, container)
			}
		}
	} else {
		filteredContainers = containers
	}

	tmpl = tmpl
	dest := os.Stdout
	if config.Dest != "" {
		dest, err = ioutil.TempFile("", "docker-gen")
		defer dest.Close()
		if err != nil {
			fmt.Println("unable to create temp file: %s\n", err)
			os.Exit(1)
		}
	}

	err = tmpl.ExecuteTemplate(dest, filepath.Base(templatePath), containers)
	if err != nil {
		fmt.Printf("template error: %s\n", err)
	}

	if config.Dest != "" {
		err = os.Rename(dest.Name(), config.Dest)
		if err != nil {
			fmt.Println("unable to create dest file %s: %s\n", config.Dest, err)
			os.Exit(1)
		}
	}

}

func newConn() (*httputil.ClientConn, error) {
	conn, err := net.Dial("unix", "/var/run/docker.sock")
	if err != nil {
		return nil, err
	}
	return httputil.NewClientConn(conn, nil), nil
}

func getEvents() chan *Event {
	eventChan := make(chan *Event, 100)
	go func() {
		defer close(eventChan)

	restart:

		c, err := newConn()
		if err != nil {
			fmt.Printf("cannot connect to docker: %s\n", err)
			return
		}
		defer c.Close()

		req, err := http.NewRequest("GET", "/events", nil)
		if err != nil {
			fmt.Printf("bad request for events: %s\n", err)
			return
		}

		resp, err := c.Do(req)
		if err != nil {
			fmt.Printf("cannot connect to events endpoint: %s\n", err)
			return
		}
		defer resp.Body.Close()

		// handle signals to stop the socket
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
		go func() {
			for sig := range sigChan {
				fmt.Printf("received signal '%v', exiting\n", sig)

				c.Close()
				close(eventChan)
				os.Exit(0)
			}
		}()

		dec := json.NewDecoder(resp.Body)
		for {
			var event *Event
			if err := dec.Decode(&event); err != nil {
				if err == io.EOF {
					break
				}
				fmt.Printf("cannot decode json: %s\n", err)
				goto restart
			}
			eventChan <- event
		}
		fmt.Printf("closing event channel\n")
	}()
	return eventChan
}

func generateFromContainers(client *docker.Client) {
	apiContainers, err := client.ListContainers(docker.ListContainersOptions{
		All: false,
	})
	if err != nil {
		fmt.Printf("error listing containers: %s\n", err)
		return
	}

	containers := []*RuntimeContainer{}
	for _, apiContainer := range apiContainers {
		container, err := client.InspectContainer(apiContainer.ID)
		if err != nil {
			fmt.Printf("error inspecting container: %s: %s\n", apiContainer.ID, err)
			continue
		}

		runtimeContainer := &RuntimeContainer{
			ID:        container.ID,
			Image:     container.Config.Image,
			Addresses: []Address{},
			Env:       make(map[string]string),
		}
		for k, _ := range container.NetworkSettings.Ports {
			runtimeContainer.Addresses = append(runtimeContainer.Addresses,
				Address{
					IP:   container.NetworkSettings.IPAddress,
					Port: k.Port(),
				})
		}

		for _, entry := range container.Config.Env {
			parts := strings.Split(entry, "=")
			runtimeContainer.Env[parts[0]] = parts[1]
		}

		containers = append(containers, runtimeContainer)
	}

	for _, config := range configs.Config {
		generateFile(config, containers)
		runNotifyCmd(config)
	}

}

func runNotifyCmd(config Config) {
	if config.NotifyCmd == "" {
		return
	}

	args := strings.Split(config.NotifyCmd, " ")
	cmd := exec.Command(args[0], args[1:]...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Printf("error running notify command: %s, %s\n", config.NotifyCmd, err)
		fmt.Println(string(out))
	}
}

func loadConfig(file string) error {
	_, err := toml.DecodeFile(file, &configs)
	if err != nil {
		return err
	}
	return nil
}

func main() {
	flag.BoolVar(&watch, "watch", false, "watch for container changes")
	flag.BoolVar(&onlyExposed, "only-exposed", false, "only include containers with exposed ports")
	flag.StringVar(&notifyCmd, "notify", "", "run command after template is regenerated")
	flag.StringVar(&configFile, "config", "", "config file with template directives")
	flag.Parse()

	if flag.NArg() < 1 && configFile == "" {
		usage()
		os.Exit(1)
	}

	if configFile != "" {
		err := loadConfig(configFile)
		if err != nil {
			fmt.Printf("error loading config %s: %s\n", configFile, err)
			os.Exit(1)
		}
	} else {
		config := Config{
			Template:    flag.Arg(0),
			Dest:        flag.Arg(1),
			Watch:       watch,
			NotifyCmd:   notifyCmd,
			OnlyExposed: onlyExposed,
		}
		configs = ConfigFile{
			Config: []Config{config}}
	}

	endpoint := "unix:///var/run/docker.sock"
	client, err := docker.NewClient(endpoint)

	if err != nil {
		panic(err)
	}

	generateFromContainers(client)

	configs = configs.filterWatches()

	if len(configs.Config) == 0 {
		return
	}

	eventChan := getEvents()
	for {
		event := <-eventChan
		if event.Status == "start" || event.Status == "stop" || event.Status == "die" {
			generateFromContainers(client)
		}
	}

}
<commit_msg>Update dest only if changed<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/fsouza/go-dockerclient"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"text/template"
)

var (
	watch       bool
	notifyCmd   string
	onlyExposed bool
	configFile  string
	configs     ConfigFile
)

type Event struct {
	ContainerId string `json:"id"`
	Status      string `json:"status"`
	Image       string `json:"from"`
}

type Address struct {
	IP   string
	Port string
}
type RuntimeContainer struct {
	ID        string
	Addresses []Address
	Image     string
	Env       map[string]string
}

type Config struct {
	Template    string
	Dest        string
	Watch       bool
	NotifyCmd   string
	OnlyExposed bool
}

type ConfigFile struct {
	Config []Config
}

func (c *ConfigFile) filterWatches() ConfigFile {
	configWithWatches := []Config{}

	for _, config := range c.Config {
		if config.Watch {
			configWithWatches = append(configWithWatches, config)
		}
	}
	return ConfigFile{
		Config: configWithWatches,
	}
}

func deepGet(item interface{}, path string) interface{} {
	if path == "" {
		return item
	}

	parts := strings.Split(path, ".")
	itemValue := reflect.ValueOf(item)

	if len(parts) > 0 {
		switch itemValue.Kind() {
		case reflect.Struct:
			fieldValue := itemValue.FieldByName(parts[0])
			if fieldValue.IsValid() {
				return deepGet(fieldValue.Interface(), strings.Join(parts[1:], "."))
			}
		case reflect.Map:
			mapValue := itemValue.MapIndex(reflect.ValueOf(parts[0]))
			if mapValue.IsValid() {
				return deepGet(mapValue.Interface(), strings.Join(parts[1:], "."))
			}
		default:
			fmt.Printf("can't group by %s\n", path)
		}
		return nil
	}

	return itemValue.Interface()
}

func groupBy(entries []*RuntimeContainer, key string) map[string][]*RuntimeContainer {
	groups := make(map[string][]*RuntimeContainer)
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			groups[value.(string)] = append(groups[value.(string)], v)
		}
	}
	return groups
}

func contains(a map[string]string, b string) bool {
	if _, ok := a[b]; ok {
		return true
	}
	return false
}

func usage() {
	println("Usage: docker-gen [-config file] [-watch=false] [-notify=\"restart xyz\"] <template> [<dest>]")
}

func generateFile(config Config, containers []*RuntimeContainer) bool {
	templatePath := config.Template
	tmpl, err := template.New(filepath.Base(templatePath)).Funcs(template.FuncMap{
		"contains": contains,
		"groupBy":  groupBy,
	}).ParseFiles(templatePath)
	if err != nil {
		panic(err)
	}

	filteredContainers := []*RuntimeContainer{}
	if config.OnlyExposed {
		for _, container := range containers {
			if len(container.Addresses) > 0 {
				filteredContainers = append(filteredContainers, container)
			}
		}
	} else {
		filteredContainers = containers
	}

	tmpl = tmpl
	dest := os.Stdout
	if config.Dest != "" {
		dest, err = ioutil.TempFile("", "docker-gen")
		defer dest.Close()
		if err != nil {
			fmt.Printf("unable to create temp file: %s\n", err)
			os.Exit(1)
		}
	}

	var buf bytes.Buffer
	multiwriter := io.MultiWriter(dest, &buf)
	err = tmpl.ExecuteTemplate(multiwriter, filepath.Base(templatePath), containers)
	if err != nil {
		fmt.Printf("template error: %s\n", err)
	}

	if config.Dest != "" {

		contents := []byte{}
		if _, err := os.Stat(config.Dest); err == nil {
			contents, err = ioutil.ReadFile(config.Dest)
			if err != nil {
				fmt.Printf("unable to compare current file contents: %s: %s\n", config.Dest, err)
				os.Exit(1)
			}
		}

		if bytes.Compare(contents, buf.Bytes()) != 0 {
			err = os.Rename(dest.Name(), config.Dest)
			if err != nil {
				fmt.Printf("unable to create dest file %s: %s\n", config.Dest, err)
				os.Exit(1)
			}
			return true
		}
		return false
	}
	return true
}

func newConn() (*httputil.ClientConn, error) {
	conn, err := net.Dial("unix", "/var/run/docker.sock")
	if err != nil {
		return nil, err
	}
	return httputil.NewClientConn(conn, nil), nil
}

func getEvents() chan *Event {
	eventChan := make(chan *Event, 100)
	go func() {
		defer close(eventChan)

	restart:

		c, err := newConn()
		if err != nil {
			fmt.Printf("cannot connect to docker: %s\n", err)
			return
		}
		defer c.Close()

		req, err := http.NewRequest("GET", "/events", nil)
		if err != nil {
			fmt.Printf("bad request for events: %s\n", err)
			return
		}

		resp, err := c.Do(req)
		if err != nil {
			fmt.Printf("cannot connect to events endpoint: %s\n", err)
			return
		}
		defer resp.Body.Close()

		// handle signals to stop the socket
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
		go func() {
			for sig := range sigChan {
				fmt.Printf("received signal '%v', exiting\n", sig)

				c.Close()
				close(eventChan)
				os.Exit(0)
			}
		}()

		dec := json.NewDecoder(resp.Body)
		for {
			var event *Event
			if err := dec.Decode(&event); err != nil {
				if err == io.EOF {
					break
				}
				fmt.Printf("cannot decode json: %s\n", err)
				goto restart
			}
			eventChan <- event
		}
		fmt.Printf("closing event channel\n")
	}()
	return eventChan
}

func generateFromContainers(client *docker.Client) {
	apiContainers, err := client.ListContainers(docker.ListContainersOptions{
		All: false,
	})
	if err != nil {
		fmt.Printf("error listing containers: %s\n", err)
		return
	}

	containers := []*RuntimeContainer{}
	for _, apiContainer := range apiContainers {
		container, err := client.InspectContainer(apiContainer.ID)
		if err != nil {
			fmt.Printf("error inspecting container: %s: %s\n", apiContainer.ID, err)
			continue
		}

		runtimeContainer := &RuntimeContainer{
			ID:        container.ID,
			Image:     container.Config.Image,
			Addresses: []Address{},
			Env:       make(map[string]string),
		}
		for k, _ := range container.NetworkSettings.Ports {
			runtimeContainer.Addresses = append(runtimeContainer.Addresses,
				Address{
					IP:   container.NetworkSettings.IPAddress,
					Port: k.Port(),
				})
		}

		for _, entry := range container.Config.Env {
			parts := strings.Split(entry, "=")
			runtimeContainer.Env[parts[0]] = parts[1]
		}

		containers = append(containers, runtimeContainer)
	}

	for _, config := range configs.Config {
		changed := generateFile(config, containers)
		if changed {
			runNotifyCmd(config)
		}

	}

}

func runNotifyCmd(config Config) {
	if config.NotifyCmd == "" {
		return
	}

	args := strings.Split(config.NotifyCmd, " ")
	cmd := exec.Command(args[0], args[1:]...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Printf("error running notify command: %s, %s\n", config.NotifyCmd, err)
		fmt.Println(string(out))
	}
}

func loadConfig(file string) error {
	_, err := toml.DecodeFile(file, &configs)
	if err != nil {
		return err
	}
	return nil
}

func main() {
	flag.BoolVar(&watch, "watch", false, "watch for container changes")
	flag.BoolVar(&onlyExposed, "only-exposed", false, "only include containers with exposed ports")
	flag.StringVar(&notifyCmd, "notify", "", "run command after template is regenerated")
	flag.StringVar(&configFile, "config", "", "config file with template directives")
	flag.Parse()

	if flag.NArg() < 1 && configFile == "" {
		usage()
		os.Exit(1)
	}

	if configFile != "" {
		err := loadConfig(configFile)
		if err != nil {
			fmt.Printf("error loading config %s: %s\n", configFile, err)
			os.Exit(1)
		}
	} else {
		config := Config{
			Template:    flag.Arg(0),
			Dest:        flag.Arg(1),
			Watch:       watch,
			NotifyCmd:   notifyCmd,
			OnlyExposed: onlyExposed,
		}
		configs = ConfigFile{
			Config: []Config{config}}
	}

	endpoint := "unix:///var/run/docker.sock"
	client, err := docker.NewClient(endpoint)

	if err != nil {
		panic(err)
	}

	generateFromContainers(client)

	configs = configs.filterWatches()

	if len(configs.Config) == 0 {
		return
	}

	eventChan := getEvents()
	for {
		event := <-eventChan
		if event.Status == "start" || event.Status == "stop" || event.Status == "die" {
			generateFromContainers(client)
		}
	}

}
<|endoftext|>


<commit_before>// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license

package irc

import (
	"time"

	"github.com/goshuirc/irc-go/ircmsg"

	"github.com/oragono/oragono/irc/caps"
	"github.com/oragono/oragono/irc/utils"
)

// MessageCache caches serialized IRC messages.
// First call Initialize or InitializeSplitMessage, which records
// the parameters and builds the cache. Then call Send, which will
// either send a cached version of the message or dispatch to another
// send routine that can synthesize the necessary version on the fly.
type MessageCache struct {
	// these cache a single-line message (e.g., JOIN, or PRIVMSG with a 512-byte message)
	// one version is "plain" (legacy clients with no tags) and one is "full" (client has
	// the message-tags cap)
	plain    []byte
	fullTags []byte
	// these cache a multiline message (a PRIVMSG that was sent as a multiline batch)
	// one version is "plain" (legacy clients with no tags) and one is "full" (client has
	// the multiline cap)
	plainMultiline    [][]byte
	fullTagsMultiline [][]byte

	time        time.Time
	msgid       string
	accountName string
	tags        map[string]string
	source      string
	command     string

	params []string

	target       string
	splitMessage utils.SplitMessage
}

func addAllTags(msg *ircmsg.IrcMessage, tags map[string]string, serverTime time.Time, msgid, accountName string) {
	msg.UpdateTags(tags)
	msg.SetTag("time", serverTime.Format(IRCv3TimestampFormat))
	if accountName != "*" {
		msg.SetTag("account", accountName)
	}
	if msgid != "" {
		msg.SetTag("msgid", msgid)
	}
}

func (m *MessageCache) handleErr(server *Server, err error) bool {
	if err != nil {
		server.logger.Error("internal", "Error assembling message for sending", err.Error())
		// blank these out so Send will be a no-op
		m.fullTags = nil
		m.fullTagsMultiline = nil
		return true
	}
	return false
}

func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
	m.time = serverTime
	m.msgid = msgid
	m.source = nickmask
	m.accountName = accountName
	m.tags = tags
	m.command = command
	m.params = params

	var msg ircmsg.IrcMessage
	config := server.Config()
	if config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[command] {
		msg.ForceTrailing()
	}
	msg.Prefix = nickmask
	msg.Command = command
	msg.Params = make([]string, len(params))
	copy(msg.Params, params)
	m.plain, err = msg.LineBytesStrict(false, MaxLineLen)
	if m.handleErr(server, err) {
		return
	}

	addAllTags(&msg, tags, serverTime, msgid, accountName)
	m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
	if m.handleErr(server, err) {
		return
	}
	return
}

func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) (err error) {
	m.time = message.Time
	m.msgid = message.Msgid
	m.source = nickmask
	m.accountName = accountName
	m.tags = tags
	m.command = command
	m.target = target
	m.splitMessage = message

	config := server.Config()
	forceTrailing := config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[command]

	if message.Is512() {
		isTagmsg := command == "TAGMSG"
		var msg ircmsg.IrcMessage
		if forceTrailing {
			msg.ForceTrailing()
		}

		msg.Prefix = nickmask
		msg.Command = command
		if isTagmsg {
			msg.Params = []string{target}
		} else {
			msg.Params = []string{target, message.Message}
		}
		m.params = msg.Params
		if !isTagmsg {
			m.plain, err = msg.LineBytesStrict(false, MaxLineLen)
			if m.handleErr(server, err) {
				return
			}
		}

		addAllTags(&msg, tags, message.Time, message.Msgid, accountName)
		m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
		if m.handleErr(server, err) {
			return
		}
	} else {
		var msg ircmsg.IrcMessage
		if forceTrailing {
			msg.ForceTrailing()
		}
		msg.Prefix = nickmask
		msg.Command = command
		msg.Params = make([]string, 2)
		msg.Params[0] = target
		m.plainMultiline = make([][]byte, len(message.Split))
		for i, pair := range message.Split {
			msg.Params[1] = pair.Message
			m.plainMultiline[i], err = msg.LineBytesStrict(false, MaxLineLen)
			if m.handleErr(server, err) {
				return
			}
		}

		// we need to send the same batch ID to all recipient sessions;
		// use a uuidv4-alike to ensure that it won't collide
		batch := composeMultilineBatch(utils.GenerateSecretToken(), nickmask, accountName, tags, command, target, message)
		m.fullTagsMultiline = make([][]byte, len(batch))
		for i, msg := range batch {
			if forceTrailing {
				msg.ForceTrailing()
			}
			m.fullTagsMultiline[i], err = msg.LineBytesStrict(false, MaxLineLen)
			if m.handleErr(server, err) {
				return
			}
		}
	}
	return
}

func (m *MessageCache) Send(session *Session) {
	if m.fullTags != nil {
		if session.capabilities.Has(caps.MessageTags) {
			session.sendBytes(m.fullTags, false)
		} else if !(session.capabilities.Has(caps.ServerTime) || session.capabilities.Has(caps.AccountTag)) {
			if m.plain != nil {
				session.sendBytes(m.plain, false)
			}
		} else {
			session.sendFromClientInternal(false, m.time, m.msgid, m.source, m.accountName, nil, m.command, m.params...)
		}
	} else if m.fullTagsMultiline != nil {
		if session.capabilities.Has(caps.Multiline) {
			for _, line := range m.fullTagsMultiline {
				session.sendBytes(line, false)
			}
		} else if !(session.capabilities.Has(caps.ServerTime) || session.capabilities.Has(caps.AccountTag)) {
			for _, line := range m.plainMultiline {
				session.sendBytes(line, false)
			}
		} else {
			session.sendSplitMsgFromClientInternal(false, m.source, m.accountName, m.tags, m.command, m.target, m.splitMessage)
		}
	}
}
<commit_msg>fix incorrect TAGMSG relaying<commit_after>// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license

package irc

import (
	"time"

	"github.com/goshuirc/irc-go/ircmsg"

	"github.com/oragono/oragono/irc/caps"
	"github.com/oragono/oragono/irc/utils"
)

// MessageCache caches serialized IRC messages.
// First call Initialize or InitializeSplitMessage, which records
// the parameters and builds the cache. Then call Send, which will
// either send a cached version of the message or dispatch to another
// send routine that can synthesize the necessary version on the fly.
type MessageCache struct {
	// these cache a single-line message (e.g., JOIN, or PRIVMSG with a 512-byte message)
	// one version is "plain" (legacy clients with no tags) and one is "full" (client has
	// the message-tags cap)
	plain    []byte
	fullTags []byte
	// these cache a multiline message (a PRIVMSG that was sent as a multiline batch)
	// one version is "plain" (legacy clients with no tags) and one is "full" (client has
	// the multiline cap)
	plainMultiline    [][]byte
	fullTagsMultiline [][]byte

	time        time.Time
	msgid       string
	accountName string
	tags        map[string]string
	source      string
	command     string

	params []string

	target       string
	splitMessage utils.SplitMessage
}

func addAllTags(msg *ircmsg.IrcMessage, tags map[string]string, serverTime time.Time, msgid, accountName string) {
	msg.UpdateTags(tags)
	msg.SetTag("time", serverTime.Format(IRCv3TimestampFormat))
	if accountName != "*" {
		msg.SetTag("account", accountName)
	}
	if msgid != "" {
		msg.SetTag("msgid", msgid)
	}
}

func (m *MessageCache) handleErr(server *Server, err error) bool {
	if err != nil {
		server.logger.Error("internal", "Error assembling message for sending", err.Error())
		// blank these out so Send will be a no-op
		m.fullTags = nil
		m.fullTagsMultiline = nil
		return true
	}
	return false
}

func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
	m.time = serverTime
	m.msgid = msgid
	m.source = nickmask
	m.accountName = accountName
	m.tags = tags
	m.command = command
	m.params = params

	var msg ircmsg.IrcMessage
	config := server.Config()
	if config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[command] {
		msg.ForceTrailing()
	}
	msg.Prefix = nickmask
	msg.Command = command
	msg.Params = make([]string, len(params))
	copy(msg.Params, params)
	m.plain, err = msg.LineBytesStrict(false, MaxLineLen)
	if m.handleErr(server, err) {
		return
	}

	addAllTags(&msg, tags, serverTime, msgid, accountName)
	m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
	if m.handleErr(server, err) {
		return
	}
	return
}

func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) (err error) {
	m.time = message.Time
	m.msgid = message.Msgid
	m.source = nickmask
	m.accountName = accountName
	m.tags = tags
	m.command = command
	m.target = target
	m.splitMessage = message

	config := server.Config()
	forceTrailing := config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[command]

	if message.Is512() {
		isTagmsg := command == "TAGMSG"
		var msg ircmsg.IrcMessage
		if forceTrailing {
			msg.ForceTrailing()
		}

		msg.Prefix = nickmask
		msg.Command = command
		if isTagmsg {
			msg.Params = []string{target}
		} else {
			msg.Params = []string{target, message.Message}
		}
		m.params = msg.Params
		if !isTagmsg {
			m.plain, err = msg.LineBytesStrict(false, MaxLineLen)
			if m.handleErr(server, err) {
				return
			}
		}

		addAllTags(&msg, tags, message.Time, message.Msgid, accountName)
		m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
		if m.handleErr(server, err) {
			return
		}
	} else {
		var msg ircmsg.IrcMessage
		if forceTrailing {
			msg.ForceTrailing()
		}
		msg.Prefix = nickmask
		msg.Command = command
		msg.Params = make([]string, 2)
		msg.Params[0] = target
		m.plainMultiline = make([][]byte, len(message.Split))
		for i, pair := range message.Split {
			msg.Params[1] = pair.Message
			m.plainMultiline[i], err = msg.LineBytesStrict(false, MaxLineLen)
			if m.handleErr(server, err) {
				return
			}
		}

		// we need to send the same batch ID to all recipient sessions;
		// use a uuidv4-alike to ensure that it won't collide
		batch := composeMultilineBatch(utils.GenerateSecretToken(), nickmask, accountName, tags, command, target, message)
		m.fullTagsMultiline = make([][]byte, len(batch))
		for i, msg := range batch {
			if forceTrailing {
				msg.ForceTrailing()
			}
			m.fullTagsMultiline[i], err = msg.LineBytesStrict(false, MaxLineLen)
			if m.handleErr(server, err) {
				return
			}
		}
	}
	return
}

func (m *MessageCache) Send(session *Session) {
	if m.fullTags != nil {
		// Initialize() path:
		if session.capabilities.Has(caps.MessageTags) {
			session.sendBytes(m.fullTags, false)
		} else if m.plain != nil {
			// plain == nil indicates a TAGMSG
			if !(session.capabilities.Has(caps.ServerTime) || session.capabilities.Has(caps.AccountTag)) {
				session.sendBytes(m.plain, false)
			} else {
				// slowpath
				session.sendFromClientInternal(false, m.time, m.msgid, m.source, m.accountName, nil, m.command, m.params...)
			}
		}
	} else if m.fullTagsMultiline != nil {
		// InitializeSplitMessage() path:
		if session.capabilities.Has(caps.Multiline) {
			for _, line := range m.fullTagsMultiline {
				session.sendBytes(line, false)
			}
		} else if !(session.capabilities.Has(caps.ServerTime) || session.capabilities.Has(caps.AccountTag)) {
			for _, line := range m.plainMultiline {
				session.sendBytes(line, false)
			}
		} else {
			// slowpath
			session.sendSplitMsgFromClientInternal(false, m.source, m.accountName, m.tags, m.command, m.target, m.splitMessage)
		}
	}
}
<|endoftext|>


<commit_before>// Fuzzy searching allows for flexibly matching a string with partial input,
// useful for filtering data very quickly based on lightweight user input.
package fuzzy

import "unicode/utf8"

// Match returns true if source matches target using a fuzzy-searching
// algorithm. Note that it doesn't implement Levenshtein distance (see
// RankMatch instead), but rather a simplified version where there's no
// approximation. The method will return true only if each character in the
// source can be found in the target and occurs after the preceding matches.
func Match(source, target string) bool {
	lenDiff := len(target) - len(source)

	if lenDiff < 0 {
		return false
	}

	if lenDiff == 0 && source == target {
		return true
	}

Outer:
	for _, r1 := range source {
		for i, r2 := range target {
			if r1 == r2 {
				target = target[i+utf8.RuneLen(r2):]
				continue Outer
			}
		}
		return false
	}

	return true
}

// Find will return a list of strings in targets that fuzzy matches source.
func Find(source string, targets []string) []string {
	var matches []string

	for _, target := range targets {
		if Match(source, target) {
			matches = append(matches, target)
		}
	}

	return matches
}

// RankMatch is similar to Match except it will measure the Levenshtein
// distance between the source and the target and return its result. If there
// was no match, it will return -1.
// Given the requirements of match, RankMatch only needs to perform a subset of
// the Levenshtein calculation, only deletions need be considered, required
// additions and substitutions would fail the match test.
func RankMatch(source, target string) int {
	lenDiff := len(target) - len(source)

	if lenDiff < 0 {
		return -1
	}

	if lenDiff == 0 && source == target {
		return 0
	}

	runeDiff := 0

Outer:
	for _, r1 := range source {
		for i, r2 := range target {
			if r1 == r2 {
				target = target[i+utf8.RuneLen(r2):]
				continue Outer
			} else {
				runeDiff++
			}
		}
		return -1
	}

	// Count up remaining char
	for len(target) > 0 {
		target = target[utf8.RuneLen(rune(target[0])):]
		runeDiff++
	}

	return runeDiff
}

// RankFind is similar to Find, except it will also rank all matches using
// Levenshtein distance.
func RankFind(source string, targets []string) ranks {
	var r ranks
	for _, target := range Find(source, targets) {
		r = append(r, Rank{
			Source:   source,
			Target:   target,
			Distance: LevenshteinDistance(source, target),
		})
	}
	return r
}

// RankFindLevenshtein is similar to RankFind, except it will determine
// approximate matches using Levenshtein distance also using maxDistance
// as a filter
func RankFindLevenshtein(source string, maxDistance int, targets []string) ranks {
	var r ranks

	for _, target := range targets {
		distance := RankMatch(source, target)
		if distance <= maxDistance {
			r = append(r, Rank{
				Source:   source,
				Target:   target,
				Distance: distance,
			})
		}
	}

	return r
}

type Rank struct {
	// Source is used as the source for matching.
	Source string
	// Target is the word matched against.
	Target string
	// Distance is the Levenshtein distance between Source and Target.
	Distance int
}

type ranks []Rank

func (r ranks) Len() int {
	return len(r)
}

func (r ranks) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

func (r ranks) Less(i, j int) bool {
	return r[i].Distance < r[j].Distance
}
<commit_msg>Include distance greater than -1<commit_after>// Fuzzy searching allows for flexibly matching a string with partial input,
// useful for filtering data very quickly based on lightweight user input.
package fuzzy

import "unicode/utf8"

// Match returns true if source matches target using a fuzzy-searching
// algorithm. Note that it doesn't implement Levenshtein distance (see
// RankMatch instead), but rather a simplified version where there's no
// approximation. The method will return true only if each character in the
// source can be found in the target and occurs after the preceding matches.
func Match(source, target string) bool {
	lenDiff := len(target) - len(source)

	if lenDiff < 0 {
		return false
	}

	if lenDiff == 0 && source == target {
		return true
	}

Outer:
	for _, r1 := range source {
		for i, r2 := range target {
			if r1 == r2 {
				target = target[i+utf8.RuneLen(r2):]
				continue Outer
			}
		}
		return false
	}

	return true
}

// Find will return a list of strings in targets that fuzzy matches source.
func Find(source string, targets []string) []string {
	var matches []string

	for _, target := range targets {
		if Match(source, target) {
			matches = append(matches, target)
		}
	}

	return matches
}

// RankMatch is similar to Match except it will measure the Levenshtein
// distance between the source and the target and return its result. If there
// was no match, it will return -1.
// Given the requirements of match, RankMatch only needs to perform a subset of
// the Levenshtein calculation, only deletions need be considered, required
// additions and substitutions would fail the match test.
func RankMatch(source, target string) int {
	lenDiff := len(target) - len(source)

	if lenDiff < 0 {
		return -1
	}

	if lenDiff == 0 && source == target {
		return 0
	}

	runeDiff := 0

Outer:
	for _, r1 := range source {
		for i, r2 := range target {
			if r1 == r2 {
				target = target[i+utf8.RuneLen(r2):]
				continue Outer
			} else {
				runeDiff++
			}
		}
		return -1
	}

	// Count up remaining char
	for len(target) > 0 {
		target = target[utf8.RuneLen(rune(target[0])):]
		runeDiff++
	}

	return runeDiff
}

// RankFind is similar to Find, except it will also rank all matches using
// Levenshtein distance.
func RankFind(source string, targets []string) ranks {
	var r ranks
	for _, target := range Find(source, targets) {
		r = append(r, Rank{
			Source:   source,
			Target:   target,
			Distance: LevenshteinDistance(source, target),
		})
	}
	return r
}

// RankFindLevenshtein is similar to RankFind, except it will determine
// approximate matches using Levenshtein distance also using maxDistance
// as a filter
func RankFindLevenshtein(source string, maxDistance int, targets []string) ranks {
	var r ranks

	for _, target := range targets {
		distance := RankMatch(source, target)
		if distance > -1 && distance <= maxDistance {
			r = append(r, Rank{
				Source:   source,
				Target:   target,
				Distance: distance,
			})
		}
	}

	return r
}

type Rank struct {
	// Source is used as the source for matching.
	Source string
	// Target is the word matched against.
	Target string
	// Distance is the Levenshtein distance between Source and Target.
	Distance int
}

type ranks []Rank

func (r ranks) Len() int {
	return len(r)
}

func (r ranks) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

func (r ranks) Less(i, j int) bool {
	return r[i].Distance < r[j].Distance
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 config_test

import (
	"github.com/jacobsa/comeback/config"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestValidate(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type ValidateTest struct {
	cfg *config.Config
}

func init() { RegisterTestSuite(&ValidateTest{}) }

func (t *ValidateTest) SetUp(i *TestInfo) {
	t.cfg = &config.Config{Jobs: make(map[string]*config.Job)}
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *ValidateTest) JobNameNotValidUtf8() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: "/a"}
	t.cfg.Jobs["foo\x80\x81\x82bar"] = &config.Job{BasePath: "/b"}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)

	ExpectThat(err, HasSubstr("name"))
	ExpectThat(err, HasSubstr("UTF-8"))
}

func (t *ValidateTest) EmptyBasePath() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: ""}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)

	ExpectThat(err, HasSubstr("base path"))
	ExpectThat(err, HasSubstr("taco"))
}

func (t *ValidateTest) BasePathNotAbsolute() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: "a"}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)

	ExpectThat(err, HasSubstr("path"))
	ExpectThat(err, HasSubstr("absolute"))
	ExpectThat(err, HasSubstr("taco"))
}

func (t *ValidateTest) BasePathNotValidUtf8() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: "/a/\x80\x81\x82"}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)

	ExpectThat(err, HasSubstr("path"))
	ExpectThat(err, HasSubstr("UTF-8"))
	ExpectThat(err, HasSubstr("taco"))
}

func (t *ValidateTest) EverythingValid() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: "/a/타코"}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)
	ExpectEq(nil, err)
}
<commit_msg>Fixed some test bugs.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 config_test

import (
	"github.com/jacobsa/comeback/config"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestValidate(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type ValidateTest struct {
	cfg *config.Config
}

func init() { RegisterTestSuite(&ValidateTest{}) }

func (t *ValidateTest) SetUp(i *TestInfo) {
	t.cfg = &config.Config{Jobs: make(map[string]*config.Job)}
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *ValidateTest) JobNameNotValidUtf8() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: "/a"}
	t.cfg.Jobs["foo\x80\x81\x82bar"] = &config.Job{BasePath: "/b"}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)

	ExpectThat(err, Error(HasSubstr("name")))
	ExpectThat(err, Error(HasSubstr("UTF-8")))
}

func (t *ValidateTest) EmptyBasePath() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: ""}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)

	ExpectThat(err, Error(HasSubstr("base path")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *ValidateTest) BasePathNotAbsolute() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: "a"}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)

	ExpectThat(err, Error(HasSubstr("path")))
	ExpectThat(err, Error(HasSubstr("absolute")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *ValidateTest) BasePathNotValidUtf8() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: "/a/\x80\x81\x82"}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)

	ExpectThat(err, Error(HasSubstr("path")))
	ExpectThat(err, Error(HasSubstr("UTF-8")))
	ExpectThat(err, Error(HasSubstr("taco")))
}

func (t *ValidateTest) EverythingValid() {
	t.cfg.Jobs["taco"] = &config.Job{BasePath: "/a/타코"}
	t.cfg.Jobs["burrito"] = &config.Job{BasePath: "/c"}

	err := config.Validate(t.cfg)
	ExpectEq(nil, err)
}
<|endoftext|>


<commit_before>package console

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

// TestIntToCheckmark unit tests IntToCheckmark.
func TestIntToCheckmark(t *testing.T) {
	assert.NotEmpty(t, IntToCheckmark(0))
	assert.NotEmpty(t, IntToCheckmark(1))
	assert.NotEqual(t, IntToCheckmark(0), IntToCheckmark(1))
}

// TestStringToCheckmark unit tests StringToCheckmark.
func TestStringToCheckmark(t *testing.T) {
	assert.NotEmpty(t, StringToCheckmark(""))
	assert.NotEmpty(t, StringToCheckmark("0"))
	assert.NotEmpty(t, StringToCheckmark("1"))

	assert.NotEqual(t, StringToCheckmark(""), StringToCheckmark("0"))
	assert.NotEqual(t, StringToCheckmark("0"), StringToCheckmark("1"))
	assert.NotEqual(t, StringToCheckmark("1"), StringToCheckmark(""))
}
<commit_msg>use subtests in checkmark test<commit_after>package console

import (
	"testing"

	"fmt"

	"github.com/stretchr/testify/assert"
)

// TestTransformToCheckmarks test the mapping of values to
// printable symbols.
func TestTransformToCheckmarks(t *testing.T) {
	testData := []struct {
		checkmark string
	}{
		{IntToCheckmark(0)},
		{IntToCheckmark(1)},
		{StringToCheckmark("")},
		{StringToCheckmark("0")},
		{StringToCheckmark("1")},
	}
	for i, testCase := range testData {
		t.Run(fmt.Sprintf("Test checkmark %d", i), func(t *testing.T) {
			assert.NotNil(t, testCase.checkmark)
			assert.NotEmpty(t, testCase.checkmark)
		})
	}
}

// TestIntToCheckmarkDisjoint test that all target symbols are different (int version).
func TestIntToCheckmarkDisjoint(t *testing.T) {
	assert.NotEqual(t, IntToCheckmark(0), IntToCheckmark(1))
}

// TestStringToCheckmarkDisjoint test that all target symbols are different (string version).
func TestStringToCheckmarkDisjoint(t *testing.T) {
	assert.NotEqual(t, StringToCheckmark(""), StringToCheckmark("0"))
	assert.NotEqual(t, StringToCheckmark("0"), StringToCheckmark("1"))
	assert.NotEqual(t, StringToCheckmark("1"), StringToCheckmark(""))
}
<|endoftext|>


<commit_before>package lib

import (
	"log"
	"strings"
	"text/scanner"
)

/**
 * Need to redo this so its more similar to how golang Flag works
 * there is a default global, but then you can make your own object if needed
 */

var replacer *strings.Replacer

func init() {
	replacer = strings.NewReplacer(dictWikipedia...)
	if replacer == nil {
		panic("unable to create strings.Replacer")
	}
}

// Diff is datastructure showing what changed in a single line
type Diff struct {
	Filename  string
	Line      int
	Column    int
	Original  string
	Corrected string
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func commonPrefixWordLength(a, b string) int {
	// re-order so len(a) <= len(b) always
	if len(a) > len(b) {
		b, a = a, b
	}
	lastWhite := 0
	for i := 0; i < len(a); i++ {
		if a[i] == ' ' || a[i] == '\t' {
			lastWhite = i
		}
		if a[i] != b[i] {
			return min(lastWhite+1, len(a))
		}
	}
	return len(a)
}

// commonSuffixWordLength
func commonSuffixWordLength(a, b string) int {
	alen, blen := len(a), len(b)
	n := min(alen, blen)
	lastWhite := 0
	for i := 0; i < n; i++ {
		if a[alen-i-1] == ' ' || a[alen-i-1] == '\t' {
			lastWhite = i
		}
		if a[alen-i-1] != b[blen-i-1] {
			return min(lastWhite+1, n)
		}
	}
	return n
}

// Return one words that are corrected in a single line
//
//
func corrected(instr, outstr string) (orig, corrected string, column int) {
	prefixLen := commonPrefixWordLength(instr, outstr)
	suffixLen := commonSuffixWordLength(instr, outstr)

	a := instr[prefixLen : len(instr)-suffixLen]
	b := outstr[prefixLen : len(outstr)-suffixLen]

	// Normal, we found the right snippet and it seems sane
	if len(a) < 30 && len(b) < 30 {
		return a, b, prefixLen
	}

	// some lines have no spaces and triggers a huge output
	// trim down
	var col int
	for i := 0; i < len(a); i++ {
		if i < len(b) && a[i] != b[i] {
			col = i - 10
			if col < 0 {
				col = 0
			}
			amax := i + 10
			if amax > len(a) {
				amax = len(a)
			}
			bmax := i + 10
			if bmax > len(b) {
				bmax = len(b)
			}
			return a[col:amax], b[col:bmax], col
		}
	}
	return a, b, col
}

// DiffLines produces a grep-like diff between two strings showing
// filename, linenum and change.  It is not meant to be a comprehensive diff.
func DiffLines(filename, input, output string) (string, []Diff) {
	var changes []Diff
	if output == input {
		return output, changes
	}
	count := 0
	// line by line to make nice output
	outlines := strings.Split(output, "\n")
	inlines := strings.Split(input, "\n")
	for i := 0; i < len(inlines); i++ {
		if inlines[i] == outlines[i] {
			continue
		}
		count++
		s1, s2, col := corrected(inlines[i], outlines[i])
		changes = append(changes, Diff{
			Filename:  filename,
			Line:      i + 1, // lines start at 1
			Column:    col,
			Original:  s1,
			Corrected: s2,
		})
	}
	return output, changes
}

// ReplaceDebug logs exactly what was matched and replaced for using
// in debugging
func ReplaceDebug(input string) string {
	for i := 0; i < len(dictWikipedia); i += 2 {
		idx := strings.Index(input, dictWikipedia[i])
		if idx != -1 {
			left := idx - 10
			if left < 0 {
				left = 0
			}
			right := idx + len(dictWikipedia[i]) + 10
			if right > len(input) {
				right = len(input)
			}
			snippet := input[left:right]
			log.Printf("Found %q in %q  (%q)", dictWikipedia[i], snippet, dictWikipedia[i+1])
		}
	}
	return Replace(input)
}

// Replace takes input string and does spelling corrections on
// commonly misspelled words.
func Replace(input string) string {
	// ok doesn't do much
	return replacer.Replace(input)
}

// ReplaceGo is a specialized routine for correcting Golang source
// files.  Currently only checks comments, not identifiers for
// spelling.
//
// Other items:
//   - check strings, but need to ignore
//      * import "statements" blocks
//      * import ( "blocks" )
//   - skip first comment (line 0) if build comment
//
func ReplaceGo(input string, debug bool) string {
	var s scanner.Scanner
	s.Init(strings.NewReader(input))
	s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanChars | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments
	lastPos := 0
	output := ""
	for {

		switch s.Scan() {
		case scanner.Comment:
			origComment := s.TokenText()

			var newComment string
			if debug {
				newComment = ReplaceDebug(origComment)
			} else {
				newComment = Replace(origComment)
			}

			if origComment != newComment {
				// s.Pos().Offset is the end of the current token
				//  subtract len(origComment) to get the start of token
				offset := s.Pos().Offset
				output = output + input[lastPos:offset-len(origComment)] + newComment
				lastPos = offset
			}
		case scanner.EOF:
			// no changes, no copies
			if lastPos == 0 {
				return input
			}
			if lastPos >= len(input) {
				return output
			}

			return output + input[lastPos:]
		}
	}
}

func inArray(haystack []string, needle string) bool {
	for _, word := range haystack {
		if needle == word {
			return true
		}
	}
	return false
}

// Ignore removes a correction rule
//   WARNING: multiple calls to this will unset the previous calls.
//    thats not so good.
func Ignore(words []string) {
	newwords := make([]string, 0, len(dictWikipedia))
	for i := 0; i < len(dictWikipedia); i += 2 {
		if inArray(words, dictWikipedia[i]) {
			continue
		}
		newwords = append(newwords, dictWikipedia[i])
		newwords = append(newwords, dictWikipedia[i+1])
	}
	replacer = strings.NewReplacer(newwords...)
	if replacer == nil {
		panic("unable to create strings.Replacer")
	}
}
<commit_msg>fix off by 1(tm)<commit_after>package lib

import (
	"log"
	"strings"
	"text/scanner"
)

/**
 * Need to redo this so its more similar to how golang Flag works
 * there is a default global, but then you can make your own object if needed
 */

var replacer *strings.Replacer

func init() {
	replacer = strings.NewReplacer(dictWikipedia...)
	if replacer == nil {
		panic("unable to create strings.Replacer")
	}
}

// Diff is datastructure showing what changed in a single line
type Diff struct {
	Filename  string
	Line      int
	Column    int
	Original  string
	Corrected string
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func commonPrefixWordLength(a, b string) int {
	// re-order so len(a) <= len(b) always
	if len(a) > len(b) {
		b, a = a, b
	}
	lastWhite := 0
	for i := 0; i < len(a); i++ {
		if a[i] == ' ' || a[i] == '\t' {
			lastWhite = i
		}
		if a[i] != b[i] {
			return min(lastWhite+1, len(a))
		}
	}
	return len(a)
}

// commonSuffixWordLength
func commonSuffixWordLength(a, b string) int {
	alen, blen := len(a), len(b)
	n := min(alen, blen)
	lastWhite := 0
	for i := 0; i < n; i++ {
		if a[alen-i-1] == ' ' || a[alen-i-1] == '\t' {
			lastWhite = i
		}
		if a[alen-i-1] != b[blen-i-1] {
			if lastWhite == 0 {
				return 0
			}
			return min(lastWhite+1, n)
		}
	}
	return n
}

// Return one words that are corrected in a single line
//
//
func corrected(instr, outstr string) (orig, corrected string, column int) {
	prefixLen := commonPrefixWordLength(instr, outstr)
	suffixLen := commonSuffixWordLength(instr, outstr)

	a := instr[prefixLen : len(instr)-suffixLen]
	b := outstr[prefixLen : len(outstr)-suffixLen]

	// Normal, we found the right snippet and it seems sane
	if len(a) < 30 && len(b) < 30 {
		return a, b, prefixLen
	}

	// some lines have no spaces and triggers a huge output
	// trim down
	var col int
	for i := 0; i < len(a); i++ {
		if i < len(b) && a[i] != b[i] {
			col = i - 10
			if col < 0 {
				col = 0
			}
			amax := i + 10
			if amax > len(a) {
				amax = len(a)
			}
			bmax := i + 10
			if bmax > len(b) {
				bmax = len(b)
			}
			return a[col:amax], b[col:bmax], col
		}
	}
	return a, b, col
}

// DiffLines produces a grep-like diff between two strings showing
// filename, linenum and change.  It is not meant to be a comprehensive diff.
func DiffLines(filename, input, output string) (string, []Diff) {
	var changes []Diff
	if output == input {
		return output, changes
	}
	count := 0
	// line by line to make nice output
	outlines := strings.Split(output, "\n")
	inlines := strings.Split(input, "\n")
	for i := 0; i < len(inlines); i++ {
		if inlines[i] == outlines[i] {
			continue
		}
		count++
		s1, s2, col := corrected(inlines[i], outlines[i])
		changes = append(changes, Diff{
			Filename:  filename,
			Line:      i + 1, // lines start at 1
			Column:    col,
			Original:  s1,
			Corrected: s2,
		})
	}
	return output, changes
}

// ReplaceDebug logs exactly what was matched and replaced for using
// in debugging
func ReplaceDebug(input string) string {
	for i := 0; i < len(dictWikipedia); i += 2 {
		idx := strings.Index(input, dictWikipedia[i])
		if idx != -1 {
			left := idx - 10
			if left < 0 {
				left = 0
			}
			right := idx + len(dictWikipedia[i]) + 10
			if right > len(input) {
				right = len(input)
			}
			snippet := input[left:right]
			log.Printf("Found %q in %q  (%q)", dictWikipedia[i], snippet, dictWikipedia[i+1])
		}
	}
	return Replace(input)
}

// Replace takes input string and does spelling corrections on
// commonly misspelled words.
func Replace(input string) string {
	// ok doesn't do much
	return replacer.Replace(input)
}

// ReplaceGo is a specialized routine for correcting Golang source
// files.  Currently only checks comments, not identifiers for
// spelling.
//
// Other items:
//   - check strings, but need to ignore
//      * import "statements" blocks
//      * import ( "blocks" )
//   - skip first comment (line 0) if build comment
//
func ReplaceGo(input string, debug bool) string {
	var s scanner.Scanner
	s.Init(strings.NewReader(input))
	s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanChars | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments
	lastPos := 0
	output := ""
	for {

		switch s.Scan() {
		case scanner.Comment:
			origComment := s.TokenText()

			var newComment string
			if debug {
				newComment = ReplaceDebug(origComment)
			} else {
				newComment = Replace(origComment)
			}

			if origComment != newComment {
				// s.Pos().Offset is the end of the current token
				//  subtract len(origComment) to get the start of token
				offset := s.Pos().Offset
				output = output + input[lastPos:offset-len(origComment)] + newComment
				lastPos = offset
			}
		case scanner.EOF:
			// no changes, no copies
			if lastPos == 0 {
				return input
			}
			if lastPos >= len(input) {
				return output
			}

			return output + input[lastPos:]
		}
	}
}

func inArray(haystack []string, needle string) bool {
	for _, word := range haystack {
		if needle == word {
			return true
		}
	}
	return false
}

// Ignore removes a correction rule
//   WARNING: multiple calls to this will unset the previous calls.
//    thats not so good.
func Ignore(words []string) {
	newwords := make([]string, 0, len(dictWikipedia))
	for i := 0; i < len(dictWikipedia); i += 2 {
		if inArray(words, dictWikipedia[i]) {
			continue
		}
		newwords = append(newwords, dictWikipedia[i])
		newwords = append(newwords, dictWikipedia[i+1])
	}
	replacer = strings.NewReplacer(newwords...)
	if replacer == nil {
		panic("unable to create strings.Replacer")
	}
}
<|endoftext|>