<commit_before>package registry

func addNodes(old, neu []*Node) []*Node {
	var nodes []*Node

	// add all new nodes
	for _, n := range neu {
		node := *n
		nodes = append(nodes, &node)
	}

	// look at old nodes
	for _, o := range old {
		var exists bool

		// check against new nodes
		for _, n := range nodes {
			// ids match then skip
			if o.Id == n.Id {
				exists = true
				break
			}
		}

		// keep old node
		if !exists {
			node := *o
			nodes = append(nodes, &node)
		}
	}

	return nodes
}

func delNodes(old, del []*Node) []*Node {
	var nodes []*Node
	for _, o := range old {
		var rem bool
		for _, n := range del {
			if o.Id == n.Id {
				rem = true
				break
			}
		}
		if !rem {
			nodes = append(nodes, o)
		}
	}
	return nodes
}

// Copy makes a copy of services
func Copy(current []*Service) []*Service {
	var services []*Service

	for _, service := range current {
		// copy service
		s := new(Service)
		*s = *service

		// copy nodes
		var nodes []*Node
		for _, node := range service.Nodes {
			n := new(Node)
			*n = *node
			nodes = append(nodes, n)
		}
		s.Nodes = nodes

		// copy endpoints
		var eps []*Endpoint
		for _, ep := range service.Endpoints {
			e := new(Endpoint)
			*e = *ep
			eps = append(eps, e)
		}
		s.Endpoints = eps

		// append service
		services = append(services, s)
	}

	return services
}

// Merge merges two lists of services and returns a new copy
func Merge(olist []*Service, nlist []*Service) []*Service {
	var srv []*Service

	for _, n := range nlist {
		var seen bool
		for _, o := range olist {
			if o.Version == n.Version {
				sp := new(Service)
				// make copy
				*sp = *o
				// set nodes
				sp.Nodes = addNodes(o.Nodes, n.Nodes)

				// mark as seen
				seen = true
				srv = append(srv, sp)
				break
			} else {
				sp := new(Service)
				// make copy
				*sp = *o
				srv = append(srv, sp)
			}
		}
		if !seen {
			srv = append(srv, Copy([]*Service{n})...)
		}
	}
	return srv
}

// Remove removes services and returns a new copy
func Remove(old, del []*Service) []*Service {
	var services []*Service

	for _, o := range old {
		srv := new(Service)
		*srv = *o

		var rem bool

		for _, s := range del {
			if srv.Version == s.Version {
				srv.Nodes = delNodes(srv.Nodes, s.Nodes)

				if len(srv.Nodes) == 0 {
					rem = true
				}
			}
		}

		if !rem {
			services = append(services, srv)
		}
	}

	return services
}
<commit_msg>Preallocate slices; avoide append() reallocations when copying data<commit_after>package registry

func addNodes(old, neu []*Node) []*Node {
	var nodes []*Node

	// add all new nodes
	for _, n := range neu {
		node := *n
		nodes = append(nodes, &node)
	}

	// look at old nodes
	for _, o := range old {
		var exists bool

		// check against new nodes
		for _, n := range nodes {
			// ids match then skip
			if o.Id == n.Id {
				exists = true
				break
			}
		}

		// keep old node
		if !exists {
			node := *o
			nodes = append(nodes, &node)
		}
	}

	return nodes
}

func delNodes(old, del []*Node) []*Node {
	var nodes []*Node
	for _, o := range old {
		var rem bool
		for _, n := range del {
			if o.Id == n.Id {
				rem = true
				break
			}
		}
		if !rem {
			nodes = append(nodes, o)
		}
	}
	return nodes
}

// Copy makes a copy of services
func Copy(current []*Service) []*Service {
	services := make([]*Service, len(current))
	for i, service := range current {
		// copy service
		s := new(Service)
		*s = *service

		// copy nodes
		nodes := make([]*Node, len(service.Nodes))
		for j, node := range service.Nodes {
			n := new(Node)
			*n = *node
			nodes[j] = n
		}
		s.Nodes = nodes

		// copy endpoints
		eps := make([]*Endpoint, len(service.Endpoints))
		for j, ep := range service.Endpoints {
			e := new(Endpoint)
			*e = *ep
			eps[j] = e
		}
		s.Endpoints = eps

		// append service
		services[i] = s
	}

	return services
}

// Merge merges two lists of services and returns a new copy
func Merge(olist []*Service, nlist []*Service) []*Service {
	var srv []*Service

	for _, n := range nlist {
		var seen bool
		for _, o := range olist {
			if o.Version == n.Version {
				sp := new(Service)
				// make copy
				*sp = *o
				// set nodes
				sp.Nodes = addNodes(o.Nodes, n.Nodes)

				// mark as seen
				seen = true
				srv = append(srv, sp)
				break
			} else {
				sp := new(Service)
				// make copy
				*sp = *o
				srv = append(srv, sp)
			}
		}
		if !seen {
			srv = append(srv, Copy([]*Service{n})...)
		}
	}
	return srv
}

// Remove removes services and returns a new copy
func Remove(old, del []*Service) []*Service {
	var services []*Service

	for _, o := range old {
		srv := new(Service)
		*srv = *o

		var rem bool

		for _, s := range del {
			if srv.Version == s.Version {
				srv.Nodes = delNodes(srv.Nodes, s.Nodes)

				if len(srv.Nodes) == 0 {
					rem = true
				}
			}
		}

		if !rem {
			services = append(services, srv)
		}
	}

	return services
}
<|endoftext|>


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

package commands

import (
	. "github.com/limetext/lime/backend"
	. "github.com/quarnster/util/text"
	"reflect"
	"testing"
)

func TestMove(t *testing.T) {
	ed := GetEditor()

	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()
	v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	v.EndEdit(e)

	type Test struct {
		in      []Region
		by      string
		extend  bool
		forward bool
		exp     []Region
		args    Args
	}

	tests := []Test{
		{
			[]Region{{1, 1}, {3, 3}, {6, 6}},
			"characters",
			false,
			true,
			[]Region{{2, 2}, {4, 4}, {7, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {6, 6}},
			"characters",
			false,
			false,
			[]Region{{0, 0}, {2, 2}, {5, 5}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			false,
			true,
			[]Region{{2, 2}, {4, 4}, {7, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			false,
			false,
			[]Region{{0, 0}, {2, 2}, {5, 5}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			true,
			true,
			[]Region{{1, 2}, {3, 4}, {10, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			true,
			false,
			[]Region{{1, 0}, {3, 2}, {10, 5}},
			nil,
		},
		{
			[]Region{{1, 3}, {3, 5}, {10, 7}},
			"characters",
			true,
			true,
			[]Region{{1, 6}, {10, 8}},
			nil,
		},
		{
			[]Region{{1, 1}},
			"stops",
			true,
			true,
			[]Region{{1, 5}},
			Args{"word_end": true},
		},
		{
			[]Region{{1, 1}},
			"stops",
			false,
			true,
			[]Region{{6, 6}},
			Args{"word_begin": true},
		},
		{
			[]Region{{6, 6}},
			"stops",
			false,
			false,
			[]Region{{0, 0}},
			Args{"word_begin": true},
		},
		{
			[]Region{{34, 34}},
			"lines",
			false,
			false,
			[]Region{{23, 23}},
			nil,
		},
		{
			[]Region{{23, 23}},
			"lines",
			false,
			false,
			[]Region{{10, 10}},
			nil,
		},
		{
			[]Region{{100, 100}},
			"lines",
			false,
			false,
			[]Region{{24, 24}},
			nil,
		},
	}
	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"by": test.by, "extend": test.extend, "forward": test.forward}
		if test.args != nil {
			for k, v := range test.args {
				args[k] = v
			}
		}
		ed.CommandHandler().RunTextCommand(v, "move", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}

	e = v.BeginEdit()
	v.Insert(e, v.Buffer().Size(), "abc")
	v.EndEdit(e)

	tests = []Test{
		{
			[]Region{{100, 100}},
			"lines",
			false,
			false,
			[]Region{{27, 27}},
			nil,
		},
	}
	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"by": test.by, "extend": test.extend, "forward": test.forward}
		if test.args != nil {
			for k, v := range test.args {
				args[k] = v
			}
		}

		ed.CommandHandler().RunTextCommand(v, "move", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}
}

func TestMoveTo(t *testing.T) {
	/*
	   Correct behavior of MoveTo:
	       - Moves each cursor directly to the indicated position.
	       - If extend, the selection will be extended in the direction of movement
	*/

	ed := GetEditor()
	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()

	v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	v.EndEdit(e)

	type MoveToTest struct {
		in     []Region
		to     string
		extend bool
		exp    []Region
	}
	vbufflen := v.Buffer().Size()
	tests := []MoveToTest{
		{
			[]Region{{vbufflen, vbufflen}},
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {vbufflen - 2, vbufflen - 2}},
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			[]Region{{vbufflen, vbufflen}, {3, 3}},
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			[]Region{{vbufflen, vbufflen}},
			"bof",
			true,
			[]Region{{vbufflen, 0}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {vbufflen - 2, vbufflen - 2}},
			"bof",
			true,
			[]Region{{vbufflen - 1, 0}},
		},
		{
			[]Region{{vbufflen, vbufflen}, {3, 3}},
			"bof",
			true,
			[]Region{{vbufflen, 0}},
		},
		{
			[]Region{{0, 0}},
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			[]Region{{0, 0}, {3, 3}},
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			[]Region{{0, 0}, {vbufflen - 4, vbufflen - 4}},
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			[]Region{{0, 0}},
			"eof",
			true,
			[]Region{{0, vbufflen}},
		},
		{
			[]Region{{0, 0}, {3, 3}},
			"eof",
			true,
			[]Region{{0, vbufflen}},
		},
		{
			[]Region{{0, 0}, {vbufflen - 4, vbufflen - 4}},
			"eof",
			true,
			[]Region{{0, vbufflen}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}},
			"bol",
			false,
			[]Region{{24, 24}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {vbufflen - 3, vbufflen - 3}},
			"bol",
			false,
			[]Region{{24, 24}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {3, 3}},
			"bol",
			false,
			[]Region{{24, 24}, {0, 0}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}},
			"bol",
			true,
			[]Region{{vbufflen - 1, 24}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {vbufflen - 3, vbufflen - 3}},
			"bol",
			true,
			[]Region{{vbufflen - 1, 24}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {3, 3}},
			"bol",
			true,
			[]Region{{vbufflen - 1, 24}, {3, 0}},
		},
		{
			[]Region{{0, 0}},
			"eol",
			false,
			[]Region{{12, 12}},
		},
		{
			[]Region{{0, 0}, {3, 3}},
			"eol",
			false,
			[]Region{{12, 12}},
		},
		{
			[]Region{{0, 0}, {vbufflen - 4, vbufflen - 4}},
			"eol",
			false,
			[]Region{{12, 12}, {vbufflen - 1, vbufflen - 1}},
		},
		{
			[]Region{{0, 0}},
			"eol",
			true,
			[]Region{{0, 12}},
		},
		{
			[]Region{{0, 0}, {3, 3}},
			"eol",
			true,
			[]Region{{0, 12}},
		},
		{
			[]Region{{0, 0}, {vbufflen - 4, vbufflen - 4}},
			"eol",
			true,
			[]Region{{0, 12}, {vbufflen - 4, vbufflen - 1}},
		},
	}

	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"to": test.to, "extend": test.extend}
		ed.CommandHandler().RunTextCommand(v, "move_to", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}
}

type scfe struct {
	DummyFrontend
	show Region
}

func (f *scfe) VisibleRegion(v *View) Region {
	s := v.Buffer().Line(v.Buffer().TextPoint(3*3, 1))
	e := v.Buffer().Line(v.Buffer().TextPoint(6*3, 1))
	return Region{s.Begin(), e.End()}
}

func (f *scfe) Show(v *View, r Region) {
	f.show = r
}

func TestScrollLines(t *testing.T) {
	var fe scfe
	ed := GetEditor()
	ed.SetFrontend(&fe)
	ch := ed.CommandHandler()
	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()
	for i := 0; i < 10; i++ {
		v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	}
	v.EndEdit(e)
	ch.RunTextCommand(v, "scroll_lines", Args{"amount": 0})

	if c := v.Buffer().Line(v.Buffer().TextPoint(3*3, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}

	ch.RunTextCommand(v, "scroll_lines", Args{"amount": 1})
	if c := v.Buffer().Line(v.Buffer().TextPoint(3*3-1, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}
	t.Log(fe.VisibleRegion(v), v.Buffer().Line(v.Buffer().TextPoint(6*3+1, 1)))
	ch.RunTextCommand(v, "scroll_lines", Args{"amount": -1})
	if c := v.Buffer().Line(v.Buffer().TextPoint(6*3+1, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}
}
<commit_msg>Improve readability and add some MoveTo tests.<commit_after>// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package commands

import (
	. "github.com/limetext/lime/backend"
	. "github.com/quarnster/util/text"
	"reflect"
	"testing"
)

func TestMove(t *testing.T) {
	ed := GetEditor()

	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()
	v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	v.EndEdit(e)

	type Test struct {
		in      []Region
		by      string
		extend  bool
		forward bool
		exp     []Region
		args    Args
	}

	tests := []Test{
		{
			[]Region{{1, 1}, {3, 3}, {6, 6}},
			"characters",
			false,
			true,
			[]Region{{2, 2}, {4, 4}, {7, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {6, 6}},
			"characters",
			false,
			false,
			[]Region{{0, 0}, {2, 2}, {5, 5}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			false,
			true,
			[]Region{{2, 2}, {4, 4}, {7, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			false,
			false,
			[]Region{{0, 0}, {2, 2}, {5, 5}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			true,
			true,
			[]Region{{1, 2}, {3, 4}, {10, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			true,
			false,
			[]Region{{1, 0}, {3, 2}, {10, 5}},
			nil,
		},
		{
			[]Region{{1, 3}, {3, 5}, {10, 7}},
			"characters",
			true,
			true,
			[]Region{{1, 6}, {10, 8}},
			nil,
		},
		{
			[]Region{{1, 1}},
			"stops",
			true,
			true,
			[]Region{{1, 5}},
			Args{"word_end": true},
		},
		{
			[]Region{{1, 1}},
			"stops",
			false,
			true,
			[]Region{{6, 6}},
			Args{"word_begin": true},
		},
		{
			[]Region{{6, 6}},
			"stops",
			false,
			false,
			[]Region{{0, 0}},
			Args{"word_begin": true},
		},
		{
			[]Region{{34, 34}},
			"lines",
			false,
			false,
			[]Region{{23, 23}},
			nil,
		},
		{
			[]Region{{23, 23}},
			"lines",
			false,
			false,
			[]Region{{10, 10}},
			nil,
		},
		{
			[]Region{{100, 100}},
			"lines",
			false,
			false,
			[]Region{{24, 24}},
			nil,
		},
	}
	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"by": test.by, "extend": test.extend, "forward": test.forward}
		if test.args != nil {
			for k, v := range test.args {
				args[k] = v
			}
		}
		ed.CommandHandler().RunTextCommand(v, "move", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}

	e = v.BeginEdit()
	v.Insert(e, v.Buffer().Size(), "abc")
	v.EndEdit(e)

	tests = []Test{
		{
			[]Region{{100, 100}},
			"lines",
			false,
			false,
			[]Region{{27, 27}},
			nil,
		},
	}
	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"by": test.by, "extend": test.extend, "forward": test.forward}
		if test.args != nil {
			for k, v := range test.args {
				args[k] = v
			}
		}

		ed.CommandHandler().RunTextCommand(v, "move", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}
}

func TestMoveTo(t *testing.T) {
	/*
	   Correct behavior of MoveTo:
	       - Moves each cursor directly to the indicated position.
	       - If extend, the selection will be extended in the direction of movement
	*/

	ed := GetEditor()
	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()

	v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	v.EndEdit(e)

	type MoveToTest struct {
		in     []Region
		to     string
		extend bool
		exp    []Region
	}

	singleCursor 			:= []Region{{16, 16}}
	sameLineCursors 		:= []Region{{16, 16}, {17, 17}}
	sameLineCursorsReversed := []Region{{17, 17}, {16, 16}}
	diffLineCursors 		:= []Region{{3, 3},   {17, 17}}
	diffLineCursorsReversed := []Region{{17, 17}, {3, 3}}

	vbufflen := v.Buffer().Size()

	tests := []MoveToTest{
		// BOF move
		{
			singleCursor,
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			sameLineCursors,
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			sameLineCursorsReversed,
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			diffLineCursors,
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			diffLineCursorsReversed,
			"bof",
			false,
			[]Region{{0, 0}},
		},

		// BOF extend
		{
			singleCursor,
			"bof",
			true,
			[]Region{{16, 0}},
		},
		{
			sameLineCursors,
			"bof",
			true,
			[]Region{{17, 0}},
		},
		{
			sameLineCursorsReversed,
			"bof",
			true,
			[]Region{{17, 0}},
		},
		{
			diffLineCursors,
			"bof",
			true,
			[]Region{{17, 0}},
		},
		{
			diffLineCursorsReversed,
			"bof",
			true,
			[]Region{{17, 0}},
		},

		// EOF move
		{
			singleCursor,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			sameLineCursors,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			sameLineCursorsReversed,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			diffLineCursors,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			diffLineCursorsReversed,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},

		// EOF extend
		{
			singleCursor,
			"eof",
			true,
			[]Region{{16, vbufflen}},
		},
		{
			sameLineCursors,
			"eof",
			true,
			[]Region{{16, vbufflen}},
		},
		{
			sameLineCursorsReversed,
			"eof",
			true,
			[]Region{{16, vbufflen}},
		},
		{
			diffLineCursors,
			"eof",
			true,
			[]Region{{3, vbufflen}},
		},
		{
			diffLineCursorsReversed,
			"eof",
			true,
			[]Region{{3, vbufflen}},
		},

		// BOL move
		{
			singleCursor,
			"bol",
			false,
			[]Region{{13, 13}},
		},
		{
			sameLineCursors,
			"bol",
			false,
			[]Region{{13, 13}},
		},
		{
			sameLineCursorsReversed,
			"bol",
			false,
			[]Region{{13, 13}},
		},
		{
			diffLineCursors,
			"bol",
			false,
			[]Region{{0, 0}, {13, 13}},
		},
		{
			diffLineCursorsReversed,
			"bol",
			false,
			[]Region{{13, 13}, {0, 0}},
		},

		// BOL extend
		{
			singleCursor,
			"bol",
			true,
			[]Region{{16, 13}},
		},
		{
			sameLineCursors,
			"bol",
			true,
			[]Region{{17, 13}},
		},
		{
			sameLineCursorsReversed,
			"bol",
			true,
			[]Region{{17, 13}},
		},
		{
			diffLineCursors,
			"bol",
			true,
			[]Region{{3, 0}, {17, 13}},
		},
		{
			diffLineCursorsReversed,
			"bol",
			true,
			[]Region{{17, 13}, {3, 0}},
		},

		// EOL move
		{
			singleCursor,
			"eol",
			false,
			[]Region{{23, 23}},
		},
		{
			sameLineCursors,
			"eol",
			false,
			[]Region{{23, 23}},
		},
		{
			sameLineCursorsReversed,
			"eol",
			false,
			[]Region{{23, 23}},
		},
		{
			diffLineCursors,
			"eol",
			false,
			[]Region{{12, 12}, {23, 23}},
		},
		{
			diffLineCursorsReversed,
			"eol",
			false,
			[]Region{{23, 23}, {12, 12}},
		},

		// EOL extend
		{
			singleCursor,
			"eol",
			true,
			[]Region{{16, 23}},
		},
		{
			sameLineCursors,
			"eol",
			true,
			[]Region{{16, 23}},
		},
		{
			sameLineCursorsReversed,
			"eol",
			true,
			[]Region{{16, 23}},
		},
		{
			diffLineCursors,
			"eol",
			true,
			[]Region{{3, 12}, {17, 23}},
		},
		{
			diffLineCursorsReversed,
			"eol",
			true,
			[]Region{{17, 23}, {3, 12}},
		},
	}

	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"to": test.to, "extend": test.extend}
		ed.CommandHandler().RunTextCommand(v, "move_to", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}
}

type scfe struct {
	DummyFrontend
	show Region
}

func (f *scfe) VisibleRegion(v *View) Region {
	s := v.Buffer().Line(v.Buffer().TextPoint(3*3, 1))
	e := v.Buffer().Line(v.Buffer().TextPoint(6*3, 1))
	return Region{s.Begin(), e.End()}
}

func (f *scfe) Show(v *View, r Region) {
	f.show = r
}

func TestScrollLines(t *testing.T) {
	var fe scfe
	ed := GetEditor()
	ed.SetFrontend(&fe)
	ch := ed.CommandHandler()
	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()
	for i := 0; i < 10; i++ {
		v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	}
	v.EndEdit(e)
	ch.RunTextCommand(v, "scroll_lines", Args{"amount": 0})

	if c := v.Buffer().Line(v.Buffer().TextPoint(3*3, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}

	ch.RunTextCommand(v, "scroll_lines", Args{"amount": 1})
	if c := v.Buffer().Line(v.Buffer().TextPoint(3*3-1, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}
	t.Log(fe.VisibleRegion(v), v.Buffer().Line(v.Buffer().TextPoint(6*3+1, 1)))
	ch.RunTextCommand(v, "scroll_lines", Args{"amount": -1})
	if c := v.Buffer().Line(v.Buffer().TextPoint(6*3+1, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}
}
<|endoftext|>


<commit_before>// Package relay provides tools for using tcprelay. It contains the messaging
// infrastructure and some othe standard librarys net interfaces for interacting
// with the relay as if it were a real TCP connection.
package relay

import "fmt"

// MessageType is the message type. It may simply signal something or help
// unmarshal the rest of the message to it's useful parts.
type MessageType int

// String returns the string representation of the given type.
func (t MessageType) String() string {
	switch t {
	case MessageTypeRelay:
		return "relay"
	case MessageTypeStop:
		return "stop"
	case MessageTypeConnect:
		return "connect"
	case MessageTypeData:
		return "data"
	case MessageTypeClose:
		return "close"
	}
	return ""
}

const (
	// MessageTypeRelay is the first message the relay sends to the server. It's
	// data is a utf8 byte-encoded string (e.g. string(m.Data)) that contains the
	// addr:port that clients can use to connect.
	MessageTypeRelay MessageType = iota

	// MessageTypeStop is a signal from the server that the relay should shutdown
	// relaying for this server.
	MessageTypeStop

	// MessageTypeConnect is a signal from the relay to the server that a new
	// conection is being made. The RemoteAddr and LocalAddr will contain the
	// client information and should be used for future communication to the
	// client.
	MessageTypeConnect

	// MessageTypeData is how the server and relay transfer data to and from
	// clients. The RemoteAddr and LocalAddr should be filled and the Data contains
	// the new data to process.
	MessageTypeData

	// MessageTypeClose is how the server and relay signal that a client is or
	// should be closed. The RemoteAddr and LocalAddr should contain the client
	// information that should be closed.
	MessageTypeClose
)

// Message is a generic message that the servers and clients use to communicate.
// If the message is from or for a client, the remote and local addresses should
// be filled.
type Message struct {
	Type       MessageType
	RemoteAddr string
	LocalAddr  string
	Data       []byte
}

// String returns a human readable version of this message.
func (m *Message) String() string {
	s := string(m.Data)
	if len(s) > 20 {
		s = s[:20]
	}
	return fmt.Sprintf("[%v %v %v %v]",
		m.Type, m.RemoteAddr, m.LocalAddr, s)
}
<commit_msg>misspell connection.<commit_after>// Package relay provides tools for using tcprelay. It contains the messaging
// infrastructure and some othe standard librarys net interfaces for interacting
// with the relay as if it were a real TCP connection.
package relay

import "fmt"

// MessageType is the message type. It may simply signal something or help
// unmarshal the rest of the message to it's useful parts.
type MessageType int

// String returns the string representation of the given type.
func (t MessageType) String() string {
	switch t {
	case MessageTypeRelay:
		return "relay"
	case MessageTypeStop:
		return "stop"
	case MessageTypeConnect:
		return "connect"
	case MessageTypeData:
		return "data"
	case MessageTypeClose:
		return "close"
	}
	return ""
}

const (
	// MessageTypeRelay is the first message the relay sends to the server. It's
	// data is a utf8 byte-encoded string (e.g. string(m.Data)) that contains the
	// addr:port that clients can use to connect.
	MessageTypeRelay MessageType = iota

	// MessageTypeStop is a signal from the server that the relay should shutdown
	// relaying for this server.
	MessageTypeStop

	// MessageTypeConnect is a signal from the relay to the server that a new
	// connection is being made. The RemoteAddr and LocalAddr will contain the
	// client information and should be used for future communication to the
	// client.
	MessageTypeConnect

	// MessageTypeData is how the server and relay transfer data to and from
	// clients. The RemoteAddr and LocalAddr should be filled and the Data contains
	// the new data to process.
	MessageTypeData

	// MessageTypeClose is how the server and relay signal that a client is or
	// should be closed. The RemoteAddr and LocalAddr should contain the client
	// information that should be closed.
	MessageTypeClose
)

// Message is a generic message that the servers and clients use to communicate.
// If the message is from or for a client, the remote and local addresses should
// be filled.
type Message struct {
	Type       MessageType
	RemoteAddr string
	LocalAddr  string
	Data       []byte
}

// String returns a human readable version of this message.
func (m *Message) String() string {
	s := string(m.Data)
	if len(s) > 20 {
		s = s[:20]
	}
	return fmt.Sprintf("[%v %v %v %v]",
		m.Type, m.RemoteAddr, m.LocalAddr, s)
}
<|endoftext|>


<commit_before>package ot

type OpType int

const (
	OpRetain OpType = iota
	OpInsert
	OpDelete
)

type Op interface {
	Type() OpType
	Span() int
}

type RetainOp int

func (p RetainOp) Type() OpType {
	return OpRetain
}

func (p RetainOp) Span() int {
	return int(p)
}

func (p RetainOp) ComposeRetain(q RetainOp) (Op, Op, Op) {
	switch {
	case p > q:
		return q, p - q, Noop

	default:
		return p, Noop, q - p
	}
}

func (p RetainOp) ComposeDelete(q DeleteOp) (Op, Op, Op) {
	switch {
	case int(p) < int(q):
		return DeleteOp(p), Noop, q - DeleteOp(p)

	default:
		return q, p - RetainOp(q), Noop
	}
}

func (p RetainOp) TransformRetain(q RetainOp) (Op, Op, Op, Op) {
	switch {
	case p > q:
		return q, q, p - q, Noop

	default:
		return p, p, Noop, q - p
	}
}

func (p RetainOp) TransformDelete(q DeleteOp) (Op, Op, Op, Op) {
	switch {
	case int(p) > int(q):
		return Noop, q, p - RetainOp(q), Noop

	case int(p) < int(q):
		return Noop, DeleteOp(p), Noop, q - DeleteOp(p)

	default:
		return Noop, DeleteOp(p), Noop, Noop
	}
}

var Noop = RetainOp(0)

type InsertOp string

func (p InsertOp) Type() OpType {
	return OpInsert
}

func (p InsertOp) Span() int {
	return len(p)
}

func (p InsertOp) ComposeRetain(q RetainOp) (Op, Op, Op) {
	switch {
	case len(p) > int(q):
		return p[:int(q)], p[int(q):], Noop

	default:
		return p, Noop, q - RetainOp(len(p))
	}
}

func (p InsertOp) ComposeDelete(q DeleteOp) (Op, Op, Op) {
	switch {
	case len(p) > int(q):
		return Noop, p[int(q):], Noop

	case len(p) < int(q):
		return Noop, Noop, q - DeleteOp(len(p))

	default:
		return Noop, Noop, Noop
	}
}

type DeleteOp int

func (p DeleteOp) Type() OpType {
	return OpDelete
}

func (p DeleteOp) Span() int {
	return int(p)
}

func (p DeleteOp) TransformRetain(q RetainOp) (Op, Op, Op, Op) {
	switch {
	case int(p) > int(q):
		return DeleteOp(q), Noop, p - DeleteOp(q), Noop

	default:
		return p, Noop, Noop, q - RetainOp(p)
	}
}

func (p DeleteOp) TransformDelete(q DeleteOp) (Op, Op, Op, Op) {
	switch {
	case p > q:
		return Noop, Noop, p - q, Noop

	case p < q:
		return Noop, Noop, Noop, q - p

	default:
		return Noop, Noop, Noop, Noop
	}
}

type RetainComposer interface {
	ComposeRetain(q RetainOp) (Op, Op, Op)
}

type DeleteComposer interface {
	ComposeDelete(q DeleteOp) (Op, Op, Op)
}

type RetainTransformer interface {
	TransformRetain(q RetainOp) (Op, Op, Op, Op)
}

type DeleteTransformer interface {
	TransformDelete(q DeleteOp) (Op, Op, Op, Op)
}
<commit_msg>Define method to identify zero operations<commit_after>package ot

type OpType int

const (
	OpRetain OpType = iota
	OpInsert
	OpDelete
)

type Op interface {
	Type() OpType
	Span() int
	IsZero() bool
}

type RetainOp int

func (p RetainOp) Type() OpType {
	return OpRetain
}

func (p RetainOp) Span() int {
	return int(p)
}

func (p RetainOp) IsZero() bool {
	return p == 0
}

func (p RetainOp) ComposeRetain(q RetainOp) (Op, Op, Op) {
	switch {
	case p > q:
		return q, p - q, Noop

	default:
		return p, Noop, q - p
	}
}

func (p RetainOp) ComposeDelete(q DeleteOp) (Op, Op, Op) {
	switch {
	case int(p) < int(q):
		return DeleteOp(p), Noop, q - DeleteOp(p)

	default:
		return q, p - RetainOp(q), Noop
	}
}

func (p RetainOp) TransformRetain(q RetainOp) (Op, Op, Op, Op) {
	switch {
	case p > q:
		return q, q, p - q, Noop

	default:
		return p, p, Noop, q - p
	}
}

func (p RetainOp) TransformDelete(q DeleteOp) (Op, Op, Op, Op) {
	switch {
	case int(p) > int(q):
		return Noop, q, p - RetainOp(q), Noop

	case int(p) < int(q):
		return Noop, DeleteOp(p), Noop, q - DeleteOp(p)

	default:
		return Noop, DeleteOp(p), Noop, Noop
	}
}

var Noop = RetainOp(0)

type InsertOp string

func (p InsertOp) Type() OpType {
	return OpInsert
}

func (p InsertOp) Span() int {
	return len(p)
}

func (p InsertOp) IsZero() bool {
	return p == ""
}

func (p InsertOp) ComposeRetain(q RetainOp) (Op, Op, Op) {
	switch {
	case len(p) > int(q):
		return p[:int(q)], p[int(q):], Noop

	default:
		return p, Noop, q - RetainOp(len(p))
	}
}

func (p InsertOp) ComposeDelete(q DeleteOp) (Op, Op, Op) {
	switch {
	case len(p) > int(q):
		return Noop, p[int(q):], Noop

	case len(p) < int(q):
		return Noop, Noop, q - DeleteOp(len(p))

	default:
		return Noop, Noop, Noop
	}
}

type DeleteOp int

func (p DeleteOp) Type() OpType {
	return OpDelete
}

func (p DeleteOp) Span() int {
	return int(p)
}

func (p DeleteOp) IsZero() bool {
	return p == 0
}

func (p DeleteOp) TransformRetain(q RetainOp) (Op, Op, Op, Op) {
	switch {
	case int(p) > int(q):
		return DeleteOp(q), Noop, p - DeleteOp(q), Noop

	default:
		return p, Noop, Noop, q - RetainOp(p)
	}
}

func (p DeleteOp) TransformDelete(q DeleteOp) (Op, Op, Op, Op) {
	switch {
	case p > q:
		return Noop, Noop, p - q, Noop

	case p < q:
		return Noop, Noop, Noop, q - p

	default:
		return Noop, Noop, Noop, Noop
	}
}

type RetainComposer interface {
	ComposeRetain(q RetainOp) (Op, Op, Op)
}

type DeleteComposer interface {
	ComposeDelete(q DeleteOp) (Op, Op, Op)
}

type RetainTransformer interface {
	TransformRetain(q RetainOp) (Op, Op, Op, Op)
}

type DeleteTransformer interface {
	TransformDelete(q DeleteOp) (Op, Op, Op, Op)
}
<|endoftext|>


<commit_before>package workerpool

import "time"

// Pacer is a goroutine rate limiter.  When concurrent goroutines call
// Pacer.Next(), the call returns in one goroutine at a time, at a rate no
// faster than one per delay time.
//
// To use Pacer, create a new Pacer giving the interval that must elapse
// between the time one task is started and the next task is started.  Then
// call WorkerPool.SubmitPaced() or WorkerPool.SubmitPacedWait().
type Pacer struct {
	delay time.Duration
	gate  chan struct{}
}

// NewPacer creates and runs a new Pacer.
func NewPacer(delay time.Duration) *Pacer {
	p := &Pacer{
		delay: delay,
		gate:  make(chan struct{}),
	}
	go p.run()
	return p
}

// Pace wraps a function in a paced function.  The returned paced function can
// then be submitted to the workerpool, using Submit or SubmitWait, and
// starting the tasks is paced according to the pacer's delay.
func (p *Pacer) Pace(task func()) func() {
	return func() {
		p.Next()
		task()
	}
}

// Next submits a run request to the gate and returns when it is time to run.
func (p *Pacer) Next() {
	// Wait for item to be read from gate.
	p.gate <- struct{}{}
	return
}

// Stop stops the Pacer from running.
func (p *Pacer) Stop() {
	close(p.gate)
}

func (p *Pacer) run() {
	// Read item from gate no faster that one per delay.
	// Reading from the unbuffered channel serves as a "tick"
	// and unblocks the writer.
	for _ = range p.gate {
		time.Sleep(p.delay)
	}
}
<commit_msg>comments<commit_after>package workerpool

import "time"

// Pacer is a goroutine rate limiter.  When concurrent goroutines call
// Pacer.Next(), the call returns in one goroutine at a time, at a rate no
// faster than one per delay time.
//
// To use Pacer, create a new Pacer giving the interval that must elapse
// between the time one task is started and the next task is started.  Then
// call Pace(), passing your task function.  A new paced task function is
// returned that can then be passed to WorkerPool's Submit() or SubmitWait().
// For example:
//
//     wp := workerpool.New(5)
//     pacer := workerpool.NewPacer(time.Second)
//
//     pacedTask := pacer.Pace(func() {
//         fmt.Println("Hello World")
//     })
//
//     wp.Submit(pacedTask)
//
// NOTE: Do not call Pacer.Stop() until all paced tasks have completed, or
// paced tasks will hang waiting for pacer to unblock them.
type Pacer struct {
	delay time.Duration
	gate  chan struct{}
}

// NewPacer creates and runs a new Pacer.
func NewPacer(delay time.Duration) *Pacer {
	p := &Pacer{
		delay: delay,
		gate:  make(chan struct{}),
	}
	go p.run()
	return p
}

// Pace wraps a function in a paced function.  The returned paced function can
// then be submitted to the workerpool, using Submit or SubmitWait, and
// starting the tasks is paced according to the pacer's delay.
func (p *Pacer) Pace(task func()) func() {
	return func() {
		p.Next()
		task()
	}
}

// Next submits a run request to the gate and returns when it is time to run.
func (p *Pacer) Next() {
	// Wait for item to be read from gate.
	p.gate <- struct{}{}
	return
}

// Stop stops the Pacer from running.  Do not call until all paced tasks have
// completed, or paced tasks will hang waiting for pacer to unblock them.
func (p *Pacer) Stop() {
	close(p.gate)
}

func (p *Pacer) run() {
	// Read item from gate no faster that one per delay.
	// Reading from the unbuffered channel serves as a "tick"
	// and unblocks the writer.
	for _ = range p.gate {
		time.Sleep(p.delay)
	}
}
<|endoftext|>


<commit_before>package pager

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	ENDPOINT = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
)

var (
	ServiceKey = ""
)

func Trigger(description string) (incidentKey string, err error) {
	return trigger(description, "", map[string]interface{}{})
}

func TriggerIncidentKey(description, key string) (incidentKey string, err error) {
	return trigger(description, key, map[string]interface{}{})
}

func TriggerWithDetails(description string, details map[string]interface{}) (incidentKey string, err error) {
	return trigger(description, "", details)
}

func TriggerIncidentKeyWithDetails(description, key string, details map[string]interface{}) (incidentKey string, err error) {
	return trigger(description, key, details)
}

func trigger(description, key string, details map[string]interface{}) (incidentKey string, err error) {
	if len(ServiceKey) == 0 {
		return "", fmt.Errorf("pager.ServiceKey not set")
	}

	payload := map[string]interface{}{
		"service_key": ServiceKey,
		"event_type":  "trigger",
		"description": description,
		"details":     details,
	}
	if len(key) > 0 {
		payload["incident_key"] = key
	}

	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}

	resp, err := http.Post(ENDPOINT, "application/json", bytes.NewReader(jsonPayload))
	defer resp.Body.Close()

	if err = errorFromResponse(resp); err != nil {
		return "", err
	}

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("PagerDuty request was successful but an error occurred while reading the response body: %s", err.Error())
	}

	respBody := map[string]string{}
	err = json.Unmarshal(bodyBytes, &respBody)
	if err != nil {
		return "", fmt.Errorf("PagerDuty request was successful but an error occurred while parsing the response body JSON: %s", err.Error())
	}

	return respBody["incident_key"], nil
}

// errorFromResponse returns an error with a helpful message if the given
// PagerDuty response is an error response.
func errorFromResponse(resp *http.Response) (err error) {
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return nil
	}

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("PagerDuty request failed (%s) and an error occurred while reading the response body: %s", resp.Status, err.Error())
	}

	respBody := map[string]interface{}{}
	err = json.Unmarshal(bodyBytes, &respBody)
	if err != nil {
		return fmt.Errorf("PagerDuty request failed (%s) and an error occurred while parsing the response body JSON: %s", resp.Status, err.Error())
	}

	respBodyError, ok := respBody["error"].(map[string]interface{})
	if !ok {
		return fmt.Errorf("PagerDuty request failed (%s) but the response JSON was missing the 'error' field.")
	}

	respBodyErrorMessage, ok := respBodyError["message"].(string)
	if !ok {
		return fmt.Errorf("PagerDuty request failed (%s) but the 'error.message' field was missing.")
	}

	return fmt.Errorf(respBodyErrorMessage)
}
<commit_msg>Make error reporting less brittle.<commit_after>package pager

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	ENDPOINT = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
)

var (
	ServiceKey = ""
)

func Trigger(description string) (incidentKey string, err error) {
	return trigger(description, "", map[string]interface{}{})
}

func TriggerIncidentKey(description, key string) (incidentKey string, err error) {
	return trigger(description, key, map[string]interface{}{})
}

func TriggerWithDetails(description string, details map[string]interface{}) (incidentKey string, err error) {
	return trigger(description, "", details)
}

func TriggerIncidentKeyWithDetails(description, key string, details map[string]interface{}) (incidentKey string, err error) {
	return trigger(description, key, details)
}

func trigger(description, key string, details map[string]interface{}) (incidentKey string, err error) {
	if len(ServiceKey) == 0 {
		return "", fmt.Errorf("pager.ServiceKey not set")
	}

	payload := map[string]interface{}{
		"service_key": ServiceKey,
		"event_type":  "trigger",
		"description": description,
		"details":     details,
	}
	if len(key) > 0 {
		payload["incident_key"] = key
	}

	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}

	resp, err := http.Post(ENDPOINT, "application/json", bytes.NewReader(jsonPayload))
	defer resp.Body.Close()

	if err = errorFromResponse(resp); err != nil {
		return "", err
	}

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("PagerDuty request was successful but an error occurred while reading the response body: %s", err.Error())
	}

	respBody := map[string]string{}
	err = json.Unmarshal(bodyBytes, &respBody)
	if err != nil {
		return "", fmt.Errorf("PagerDuty request was successful but an error occurred while parsing the response body JSON: %s", err.Error())
	}

	return respBody["incident_key"], nil
}

// errorFromResponse returns an error with a helpful message if the given
// PagerDuty response is an error response.
func errorFromResponse(resp *http.Response) (err error) {
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return nil
	}

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("PagerDuty request failed (%s) and an error occurred while reading the response body: %s", resp.Status, err.Error())
	}

	return fmt.Errorf("PagerDuty request failed (%s): %s", resp.Status, string(bodyBytes))
}
<|endoftext|>


<commit_before>package update

import (
	"sort"
	"sync"
	"time"

	"github.com/arbovm/levenshtein"
	"github.com/itchio/ox"

	"github.com/itchio/butler/manager"

	"crawshaw.io/sqlite"
	"github.com/go-xorm/builder"
	"github.com/itchio/butler/butlerd"
	"github.com/itchio/butler/butlerd/messages"
	"github.com/itchio/butler/cmd/operate"
	"github.com/itchio/butler/cmd/operate/memorylogger"
	"github.com/itchio/butler/database/models"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/hades"
	"github.com/itchio/wharf/state"
	"github.com/pkg/errors"
)

func Register(router *butlerd.Router) {
	messages.CheckUpdate.Register(router, CheckUpdate)
	messages.SnoozeCave.Register(router, SnoozeCave)
}

func CheckUpdate(rc *butlerd.RequestContext, params butlerd.CheckUpdateParams) (*butlerd.CheckUpdateResult, error) {
	startTime := time.Now()

	consumer := rc.Consumer
	res := &butlerd.CheckUpdateResult{}

	updateParams := checkUpdateCaveParams{
		rc:      rc,
		runtime: ox.CurrentRuntime(),
	}

	var caves []*models.Cave
	cond := builder.NewCond()
	if len(params.CaveIDs) > 0 {
		updateParams.ignoreSnooze = true

		var caveIDs []interface{}
		for _, cid := range params.CaveIDs {
			caveIDs = append(caveIDs, cid)
		}
		cond = builder.In("caves.id", caveIDs...)
	}

	search := hades.Search{}.OrderBy("caves.last_touched_at DESC")
	rc.WithConn(func(conn *sqlite.Conn) {
		models.MustSelect(conn, &caves, cond, search)
		models.PreloadCaves(conn, caves)
	})

	consumer.Infof("Looking for updates to %d items...", len(caves))
	consumer.Infof("...for runtime %s", updateParams.runtime)

	var doneCaves int

	// protects 'res' and 'doneCaves'
	var resultMutex sync.Mutex
	type taskSpec struct {
		cave *models.Cave
	}

	taskSpecs := make(chan taskSpec)
	workerDone := make(chan struct{})
	numWorkers := 4

	processOne := func(spec taskSpec) {
		ml := memorylogger.New()
		update, err := checkUpdateCave(updateParams, ml.Consumer(), spec.cave)
		resultMutex.Lock()
		defer resultMutex.Unlock()

		doneCaves++
		consumer.Progress(float64(doneCaves) / float64(len(caves)))

		if err != nil {
			res.Warnings = append(res.Warnings, err.Error())
			consumer.Warnf("An update check failed: %+v", err)
			consumer.Warnf("Log follows ====================")
			ml.Copy(consumer)
			consumer.Warnf("Log ends here ==================")
		} else {
			if params.Verbose {
				ml.Copy(consumer)
			}
			if update != nil {
				res.Updates = append(res.Updates, update)
				err := messages.GameUpdateAvailable.Notify(rc, butlerd.GameUpdateAvailableNotification{
					Update: update,
				})
				if err != nil {
					consumer.Warnf("Could not send GameUpdateAvailable notification: %s", err.Error())
				}
			}
		}
	}

	work := func() {
		defer func() {
			workerDone <- struct{}{}
		}()

		for spec := range taskSpecs {
			processOne(spec)
		}
	}

	go func() {
		for _, cave := range caves {
			spec := taskSpec{
				cave: cave,
			}

			select {
			case taskSpecs <- spec:
				// good!
			case <-rc.Ctx.Done():
				close(taskSpecs)
				return
			}
		}

		close(taskSpecs)
	}()

	rc.StartProgress()
	for i := 0; i < numWorkers; i++ {
		go work()
	}

	for i := 0; i < numWorkers; i++ {
		<-workerDone
	}
	rc.EndProgress()

	consumer.Statf("Checked %d entries in %s", len(caves), time.Since(startTime))

	return res, nil
}

type checkUpdateCaveParams struct {
	ignoreSnooze bool
	rc           *butlerd.RequestContext
	runtime      *ox.Runtime
}

func checkUpdateCave(params checkUpdateCaveParams, consumer *state.Consumer, cave *models.Cave) (*butlerd.GameUpdate, error) {
	rc := params.rc
	runtime := params.runtime

	var access *operate.GameAccess
	rc.WithConn(func(conn *sqlite.Conn) {
		access = operate.AccessForGameID(conn, cave.GameID)
	})
	client := rc.Client(access.APIKey)

	if cave.Game == nil {
		consumer.Opf("Cave game is missing, trying to fetch it...")

		gameRes, err := client.GetGame(itchio.GetGameParams{
			GameID:      cave.GameID,
			Credentials: access.Credentials,
		})
		if err != nil {
			return nil, errors.Wrap(err, "Cave missing game, and error'd when fetching it")
		}

		cave.Game = gameRes.Game
		rc.WithConn(func(conn *sqlite.Conn) {
			models.MustSave(conn, gameRes.Game)
		})
	}

	if cave.Upload == nil {
		consumer.Infof("Cave upload is missing, trying to fetch it...")
		consumer.Infof("(For game %s)", operate.GameToString(cave.Game))

		uploadRes, err := client.GetUpload(itchio.GetUploadParams{
			UploadID:    cave.UploadID,
			Credentials: access.Credentials,
		})
		if err != nil {
			cave.Upload = &itchio.Upload{
				ID: cave.UploadID,
			}
			consumer.Warnf("Can't retrieve upload %d: %v", cave.UploadID, err)
			consumer.Warnf("Continuing with a placeholder upload (this may give poor results)")
		} else {
			cave.Upload = uploadRes.Upload
			rc.WithConn(func(conn *sqlite.Conn) {
				models.MustSave(conn, uploadRes.Upload)
			})
		}
	}

	consumer.Statf("Checking for updates to (%s)", operate.GameToString(cave.Game))

	if access.Credentials.DownloadKeyID > 0 {
		consumer.Infof("→ Has download key (game is owned)")
	} else {
		consumer.Infof("→ Searching without download key")
	}
	consumer.Infof("→ Last install operation at (%s)", cave.InstalledAt)

	consumer.Infof("→ Cached upload:")
	operate.LogUpload(consumer, cave.Upload, cave.Build)

	listUploadsRes, err := client.ListGameUploads(itchio.ListGameUploadsParams{
		GameID: cave.Game.ID,
	})
	if err != nil {
		return nil, errors.WithStack(err)
	}

	var currentUpload = cave.Upload
	var freshUpload *itchio.Upload
	var newerUploads []*itchio.Upload

	var referenceDate = cave.InstalledAt
	respectSnooze := !params.ignoreSnooze
	if respectSnooze && cave.SnoozedAt != nil {
		referenceDate = cave.SnoozedAt
		consumer.Infof("(Using reference snooze date (%s))", referenceDate)
	}

	for _, u := range listUploadsRes.Uploads {
		if u.ID == currentUpload.ID {
			consumer.Infof("✓ Installed upload still listed")
			freshUpload = u
			continue
		}

		if u.UpdatedAt == nil {
			consumer.Infof("↷ Skipping (nil updatedAt)")
			operate.LogUpload(consumer, u, u.Build)
			continue
		}

		if currentUpload.Type != "" && u.Type != currentUpload.Type {
			consumer.Infof("↷ Skipping (has type (%s) instead of (%s))", u.Type, currentUpload.Type)
			operate.LogUpload(consumer, u, u.Build)
			continue
		}

		if !moreRecentThan(u.UpdatedAt, referenceDate) {
			consumer.Infof("↷ Skipping (not more recent than reference date (%s))", referenceDate)
			operate.LogUpload(consumer, u, u.Build)
			continue
		}

		newerUploads = append(newerUploads, u)
	}

	// wharf updates
	if currentUpload.ChannelName != "" {
		consumer.Infof("We're currently on a wharf channel (%s)", currentUpload.ChannelName)

		if freshUpload != nil {
			consumer.Infof("...and our current upload still exists! Comparing builds...")
			if freshUpload.Build == nil {
				return nil, errors.New("We have a build installed but fresh upload has none. This shouldn't happen")
			}

			if freshUpload.Build.ID > cave.Build.ID {
				consumer.Statf("↑ A more recent build (#%d) than ours (#%d) is available, it's an update!",
					freshUpload.Build.ID,
					cave.Build.ID,
				)
				res := &butlerd.GameUpdate{
					CaveID: cave.ID,
					Game:   cave.Game,
					Direct: true,
				}

				res.Choices = append(res.Choices, &butlerd.GameUpdateChoice{
					Upload:     freshUpload,
					Build:      freshUpload.Build,
					Confidence: 1,
				})
				return res, nil
			}
			consumer.Infof("No direct update found, let's get fuzzy")
		}
	}

	// non-wharf updates
	if len(newerUploads) == 0 {
		consumer.Infof("No update found (no candidates)")
		return nil, nil
	}

	countBeforeNarrow := len(newerUploads)
	narrowDownResult := manager.NarrowDownUploads(consumer, newerUploads, runtime)
	newerUploads = narrowDownResult.Uploads
	consumer.Infof("→ %d uploads to consider (%d eliminated by narrow-down)", len(newerUploads), len(newerUploads)-countBeforeNarrow)

	if len(newerUploads) == 0 {
		consumer.Infof("No update found (no candidates)")
		return nil, nil
	}

	res := &butlerd.GameUpdate{
		CaveID: cave.ID,
		Game:   cave.Game,
		Direct: false,
	}

	consumer.Infof("→ Considered uploads:")
	for _, u := range newerUploads {
		operate.LogUpload(consumer, u, u.Build)

		var lhs, rhs string
		if u.DisplayName != "" && currentUpload.DisplayName != "" {
			lhs = u.DisplayName
			rhs = currentUpload.DisplayName
		} else {
			lhs = u.Filename
			rhs = currentUpload.Filename
		}
		dist := levenshtein.Distance(lhs, rhs)
		consumer.Infof("(distance of %d between (%s) and (%s))", dist, lhs, rhs)
		confidence := 1.0 / (1.0 + float64(dist)/10.0)

		choice := &butlerd.GameUpdateChoice{
			Upload:     u,
			Build:      u.Build,
			Confidence: confidence,
		}
		res.Choices = append(res.Choices, choice)
	}

	consumer.Infof("Sorting by confidence...")
	sort.Slice(res.Choices, func(i, j int) bool {
		ii := res.Choices[i]
		jj := res.Choices[j]
		// sort from most confidence to least confidence
		return ii.Confidence > jj.Confidence
	})

	consumer.Infof("→ Final draft:")
	for _, c := range res.Choices {
		operate.LogUpload(consumer, c.Upload, c.Build)
	}

	return res, nil
}

func moreRecentThan(lhs *time.Time, rhs *time.Time) bool {
	if lhs == nil {
		// always less recent if we lack a timestamp
		return false
	}

	if rhs == nil {
		// always more recent than something that lacks a timestamp
		return true
	}

	return (*lhs).After(*rhs)
}

func SnoozeCave(rc *butlerd.RequestContext, params butlerd.SnoozeCaveParams) (*butlerd.SnoozeCaveResult, error) {
	conn := rc.GetConn()
	defer rc.PutConn(conn)

	cave := models.CaveByID(conn, params.CaveID)
	if cave == nil {
		return nil, errors.Errorf("No such cave (%s)", params.CaveID)
	}

	now := time.Now().UTC()
	cave.SnoozedAt = &now
	cave.Save(conn)

	return &butlerd.SnoozeCaveResult{}, nil
}
<commit_msg>Closes https://github.com/itchio/itch/issues/1981 closes https://github.com/itchio/itch/issues/1982<commit_after>package update

import (
	"fmt"
	"sort"
	"sync"
	"time"

	"github.com/arbovm/levenshtein"
	"github.com/itchio/ox"

	"github.com/itchio/butler/manager"

	"crawshaw.io/sqlite"
	"github.com/go-xorm/builder"
	"github.com/itchio/butler/butlerd"
	"github.com/itchio/butler/butlerd/messages"
	"github.com/itchio/butler/cmd/operate"
	"github.com/itchio/butler/cmd/operate/memorylogger"
	"github.com/itchio/butler/database/models"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/hades"
	"github.com/itchio/wharf/state"
	"github.com/pkg/errors"
)

func Register(router *butlerd.Router) {
	messages.CheckUpdate.Register(router, CheckUpdate)
	messages.SnoozeCave.Register(router, SnoozeCave)
}

func CheckUpdate(rc *butlerd.RequestContext, params butlerd.CheckUpdateParams) (*butlerd.CheckUpdateResult, error) {
	startTime := time.Now()

	consumer := rc.Consumer
	res := &butlerd.CheckUpdateResult{}

	updateParams := checkUpdateCaveParams{
		rc:      rc,
		runtime: ox.CurrentRuntime(),
	}

	var caves []*models.Cave
	cond := builder.NewCond()
	if len(params.CaveIDs) > 0 {
		updateParams.ignoreSnooze = true

		var caveIDs []interface{}
		for _, cid := range params.CaveIDs {
			caveIDs = append(caveIDs, cid)
		}
		cond = builder.In("caves.id", caveIDs...)
	}

	search := hades.Search{}.OrderBy("caves.last_touched_at DESC")
	rc.WithConn(func(conn *sqlite.Conn) {
		models.MustSelect(conn, &caves, cond, search)
		models.PreloadCaves(conn, caves)
	})

	consumer.Infof("Looking for updates to %d items...", len(caves))
	consumer.Infof("...for runtime %s", updateParams.runtime)

	var doneCaves int

	// protects 'res' and 'doneCaves'
	var resultMutex sync.Mutex
	type taskSpec struct {
		cave *models.Cave
	}

	taskSpecs := make(chan taskSpec)
	workerDone := make(chan struct{})
	numWorkers := 4

	processOne := func(spec taskSpec) {
		defer func() {
			var err error
			if r := recover(); r != nil {
				if rErr, ok := r.(error); ok {
					err = errors.WithStack(rErr)
				} else {
					err = errors.Errorf("panic: %v", r)
				}
				consumer.Errorf("%+v", err)
				resultMutex.Lock()
				defer resultMutex.Unlock()
				res.Warnings = append(res.Warnings, fmt.Sprintf("%+v", err))
			}
		}()

		ml := memorylogger.New()
		update, err := checkUpdateCave(updateParams, ml.Consumer(), spec.cave)
		resultMutex.Lock()
		defer resultMutex.Unlock()

		doneCaves++
		consumer.Progress(float64(doneCaves) / float64(len(caves)))

		if err != nil {
			res.Warnings = append(res.Warnings, err.Error())
			consumer.Warnf("An update check failed: %+v", err)
			consumer.Warnf("Log follows ====================")
			ml.Copy(consumer)
			consumer.Warnf("Log ends here ==================")
		} else {
			if params.Verbose {
				ml.Copy(consumer)
			}
			if update != nil {
				res.Updates = append(res.Updates, update)
				err := messages.GameUpdateAvailable.Notify(rc, butlerd.GameUpdateAvailableNotification{
					Update: update,
				})
				if err != nil {
					consumer.Warnf("Could not send GameUpdateAvailable notification: %s", err.Error())
				}
			}
		}
	}

	work := func() {
		defer func() {
			workerDone <- struct{}{}
		}()

		for spec := range taskSpecs {
			processOne(spec)
		}
	}

	go func() {
		for _, cave := range caves {
			spec := taskSpec{
				cave: cave,
			}

			select {
			case taskSpecs <- spec:
				// good!
			case <-rc.Ctx.Done():
				close(taskSpecs)
				return
			}
		}

		close(taskSpecs)
	}()

	rc.StartProgress()
	for i := 0; i < numWorkers; i++ {
		go work()
	}

	for i := 0; i < numWorkers; i++ {
		<-workerDone
	}
	rc.EndProgress()

	consumer.Statf("Checked %d entries in %s", len(caves), time.Since(startTime))

	return res, nil
}

type checkUpdateCaveParams struct {
	ignoreSnooze bool
	rc           *butlerd.RequestContext
	runtime      *ox.Runtime
}

func checkUpdateCave(params checkUpdateCaveParams, consumer *state.Consumer, cave *models.Cave) (*butlerd.GameUpdate, error) {
	rc := params.rc
	runtime := params.runtime

	var access *operate.GameAccess
	rc.WithConn(func(conn *sqlite.Conn) {
		access = operate.AccessForGameID(conn, cave.GameID)
	})
	client := rc.Client(access.APIKey)

	if cave.Game == nil {
		consumer.Opf("Cave game is missing, trying to fetch it...")

		gameRes, err := client.GetGame(itchio.GetGameParams{
			GameID:      cave.GameID,
			Credentials: access.Credentials,
		})
		if err != nil {
			return nil, errors.Wrap(err, "Cave missing game, and error'd when fetching it")
		}

		cave.Game = gameRes.Game
		rc.WithConn(func(conn *sqlite.Conn) {
			models.MustSave(conn, gameRes.Game)
		})
	}

	if cave.Upload == nil {
		consumer.Infof("Cave upload is missing, trying to fetch it...")
		consumer.Infof("(For game %s)", operate.GameToString(cave.Game))

		uploadRes, err := client.GetUpload(itchio.GetUploadParams{
			UploadID:    cave.UploadID,
			Credentials: access.Credentials,
		})
		if err != nil {
			cave.Upload = &itchio.Upload{
				ID: cave.UploadID,
			}
			consumer.Warnf("Can't retrieve upload %d: %v", cave.UploadID, err)
			consumer.Warnf("Continuing with a placeholder upload (this may give poor results)")
		} else {
			cave.Upload = uploadRes.Upload
			rc.WithConn(func(conn *sqlite.Conn) {
				models.MustSave(conn, uploadRes.Upload)
			})
		}
	}

	consumer.Statf("Checking for updates to (%s)", operate.GameToString(cave.Game))

	if access.Credentials.DownloadKeyID > 0 {
		consumer.Infof("→ Has download key (game is owned)")
	} else {
		consumer.Infof("→ Searching without download key")
	}
	consumer.Infof("→ Last install operation at (%s)", cave.InstalledAt)

	consumer.Infof("→ Cached upload:")
	operate.LogUpload(consumer, cave.Upload, cave.Build)

	listUploadsRes, err := client.ListGameUploads(itchio.ListGameUploadsParams{
		GameID: cave.Game.ID,
	})
	if err != nil {
		return nil, errors.WithStack(err)
	}

	var currentUpload = cave.Upload
	var freshUpload *itchio.Upload
	var newerUploads []*itchio.Upload

	var referenceDate = cave.InstalledAt
	respectSnooze := !params.ignoreSnooze
	if respectSnooze && cave.SnoozedAt != nil {
		referenceDate = cave.SnoozedAt
		consumer.Infof("(Using reference snooze date (%s))", referenceDate)
	}

	for _, u := range listUploadsRes.Uploads {
		if u.ID == currentUpload.ID {
			consumer.Infof("✓ Installed upload still listed")
			freshUpload = u
			continue
		}

		if u.UpdatedAt == nil {
			consumer.Infof("↷ Skipping (nil updatedAt)")
			operate.LogUpload(consumer, u, u.Build)
			continue
		}

		if currentUpload.Type != "" && u.Type != currentUpload.Type {
			consumer.Infof("↷ Skipping (has type (%s) instead of (%s))", u.Type, currentUpload.Type)
			operate.LogUpload(consumer, u, u.Build)
			continue
		}

		if !moreRecentThan(u.UpdatedAt, referenceDate) {
			consumer.Infof("↷ Skipping (not more recent than reference date (%s))", referenceDate)
			operate.LogUpload(consumer, u, u.Build)
			continue
		}

		newerUploads = append(newerUploads, u)
	}

	// wharf updates
	if currentUpload.ChannelName != "" {
		consumer.Infof("We're currently on a wharf channel (%s)", currentUpload.ChannelName)

		if freshUpload != nil {
			consumer.Infof("...and our current upload still exists! Comparing builds...")
			if freshUpload.Build == nil {
				return nil, errors.New("We have a build installed but fresh upload has none. This shouldn't happen")
			}

			if freshUpload.Build.ID > cave.BuildID {
				consumer.Statf("↑ A more recent build (#%d) than ours (#%d) is available, it's an update!",
					freshUpload.Build.ID,
					cave.BuildID,
				)
				res := &butlerd.GameUpdate{
					CaveID: cave.ID,
					Game:   cave.Game,
					Direct: true,
				}

				res.Choices = append(res.Choices, &butlerd.GameUpdateChoice{
					Upload:     freshUpload,
					Build:      freshUpload.Build,
					Confidence: 1,
				})
				return res, nil
			}
			consumer.Infof("No direct update found, let's get fuzzy")
		}
	}

	// non-wharf updates
	if len(newerUploads) == 0 {
		consumer.Infof("No update found (no candidates)")
		return nil, nil
	}

	countBeforeNarrow := len(newerUploads)
	narrowDownResult := manager.NarrowDownUploads(consumer, newerUploads, runtime)
	newerUploads = narrowDownResult.Uploads
	consumer.Infof("→ %d uploads to consider (%d eliminated by narrow-down)", len(newerUploads), len(newerUploads)-countBeforeNarrow)

	if len(newerUploads) == 0 {
		consumer.Infof("No update found (no candidates)")
		return nil, nil
	}

	res := &butlerd.GameUpdate{
		CaveID: cave.ID,
		Game:   cave.Game,
		Direct: false,
	}

	consumer.Infof("→ Considered uploads:")
	for _, u := range newerUploads {
		operate.LogUpload(consumer, u, u.Build)

		var lhs, rhs string
		if u.DisplayName != "" && currentUpload.DisplayName != "" {
			lhs = u.DisplayName
			rhs = currentUpload.DisplayName
		} else {
			lhs = u.Filename
			rhs = currentUpload.Filename
		}
		dist := levenshtein.Distance(lhs, rhs)
		consumer.Infof("(distance of %d between (%s) and (%s))", dist, lhs, rhs)
		confidence := 1.0 / (1.0 + float64(dist)/10.0)

		choice := &butlerd.GameUpdateChoice{
			Upload:     u,
			Build:      u.Build,
			Confidence: confidence,
		}
		res.Choices = append(res.Choices, choice)
	}

	consumer.Infof("Sorting by confidence...")
	sort.Slice(res.Choices, func(i, j int) bool {
		ii := res.Choices[i]
		jj := res.Choices[j]
		// sort from most confidence to least confidence
		return ii.Confidence > jj.Confidence
	})

	consumer.Infof("→ Final draft:")
	for _, c := range res.Choices {
		operate.LogUpload(consumer, c.Upload, c.Build)
	}

	return res, nil
}

func moreRecentThan(lhs *time.Time, rhs *time.Time) bool {
	if lhs == nil {
		// always less recent if we lack a timestamp
		return false
	}

	if rhs == nil {
		// always more recent than something that lacks a timestamp
		return true
	}

	return (*lhs).After(*rhs)
}

func SnoozeCave(rc *butlerd.RequestContext, params butlerd.SnoozeCaveParams) (*butlerd.SnoozeCaveResult, error) {
	conn := rc.GetConn()
	defer rc.PutConn(conn)

	cave := models.CaveByID(conn, params.CaveID)
	if cave == nil {
		return nil, errors.Errorf("No such cave (%s)", params.CaveID)
	}

	now := time.Now().UTC()
	cave.SnoozedAt = &now
	cave.Save(conn)

	return &butlerd.SnoozeCaveResult{}, nil
}
<|endoftext|>


<commit_before>package naglevelparse

import (
	"errors"
	kb "github.com/jamesandariese/koalaburrito"
	"math"
	"strconv"
)

type Comparator struct {
	min float64
	max float64
	at  bool
}

func parseNagiosFloat(s string, def float64) float64 {
	if s == "~" {
		return math.Inf(-1)
	}
	if s == "" {
		return def
	}
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		// this should be impossible.
		panic(err)
	}
	return f
}

func (c *Comparator) Compare(value float64) bool {
	rv := false
	if value < c.min || value > c.max {
		rv = true
	}
	if c.at {
		rv = !rv
	}
	return rv
}

var EmptyInputError = errors.New("empty string is not a valid range")
var ParseError = errors.New("pattern has unrecognized components")

func Compile(f string) (*Comparator, error) {
	c := &Comparator{}

	t := kb.MakeTokenizer()
	t.AddPattern(`([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+|[0-9]+))`, "NUMBER")
	t.AddPattern("~", "TILDE")
	t.AddPattern("@", "AT")
	t.AddPattern(":", "COLON")

	offset := 0

	tokens, err := t.MatchAll(f)
	if err != nil {
		return nil, err
	}
	if len(tokens) < 1 {
		return nil, EmptyInputError
	}

	c.at = false
	if tokens[0].Value() == "@" {
		c.at = true
		offset += 1
	}
	if len(tokens)-offset == 3 {
		if tokens[offset+1].Name() == "COLON" {
			if tokens[offset+0].Name() == "NUMBER" {
				c.min, _ = strconv.ParseFloat(tokens[offset+0].Value(), 64)
				if tokens[offset+2].Name() == "NUMBER" {
					c.max, _ = strconv.ParseFloat(tokens[offset+2].Value(), 64)
					return c, nil
				} else if tokens[offset+2].Name() == "TILDE" {
					c.max = math.Inf(-1)
					return c, nil
				}
			} else if tokens[offset+0].Name() == "TILDE" {
				c.min = math.Inf(-1)
				if tokens[offset+2].Name() == "NUMBER" {
					c.max, _ = strconv.ParseFloat(tokens[offset+2].Value(), 64)
					return c, nil
				} else if tokens[offset+2].Name() == "TILDE" {
					c.max = math.Inf(-1)
					return c, nil
				}
			}
		}
	} else if len(tokens)-offset == 2 {
		if tokens[offset+0].Name() == "COLON" {
			c.min = 0
			if tokens[offset+1].Name() == "NUMBER" {
				c.max, _ = strconv.ParseFloat(tokens[offset+1].Value(), 64)
				return c, nil
			} else if tokens[offset+1].Name() == "TILDE" {
				c.max = math.Inf(-1)
				return c, nil
			}
		} else if tokens[offset+1].Name() == "COLON" {
			c.max = math.Inf(1)
			if tokens[offset+0].Name() == "NUMBER" {
				c.min, _ = strconv.ParseFloat(tokens[offset+0].Value(), 64)
				return c, nil
			} else if tokens[offset+0].Name() == "TILDE" {
				c.min = math.Inf(-1)
				return c, nil
			}
		}
	} else if len(tokens)-offset == 1 {
		c.min = 0
		if tokens[offset+0].Name() == "NUMBER" {
			c.max, _ = strconv.ParseFloat(tokens[offset+0].Value(), 64)
			return c, nil
		} else if tokens[offset+0].Name() == "TILDE" {
			c.max = math.Inf(-1)
			return c, nil
		}
	}
	return nil, ParseError
}
<commit_msg>Updated to match kb API change<commit_after>package naglevelparse

import (
	"errors"
	kb "github.com/jamesandariese/koalaburrito"
	"math"
	"strconv"
)

type Comparator struct {
	min float64
	max float64
	at  bool
}

func parseNagiosFloat(s string, def float64) float64 {
	if s == "~" {
		return math.Inf(-1)
	}
	if s == "" {
		return def
	}
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		// this should be impossible.
		panic(err)
	}
	return f
}

func (c *Comparator) Compare(value float64) bool {
	rv := false
	if value < c.min || value > c.max {
		rv = true
	}
	if c.at {
		rv = !rv
	}
	return rv
}

var EmptyInputError = errors.New("empty string is not a valid range")
var ParseError = errors.New("pattern has unrecognized components")

func Compile(f string) (*Comparator, error) {
	c := &Comparator{}

	t := kb.MakeTokenizer()
	t.AddPattern(`([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+|[0-9]+))`, "NUMBER")
	t.AddPattern("~", "TILDE")
	t.AddPattern("@", "AT")
	t.AddPattern(":", "COLON")

	offset := 0

	tokens, err := t.MatchAll(f)
	if err != nil {
		return nil, err
	}
	if len(tokens) < 1 {
		return nil, EmptyInputError
	}

	c.at = false
	if tokens[0].String() == "@" {
		c.at = true
		offset += 1
	}
	if len(tokens)-offset == 3 {
		if tokens[offset+1].Type() == "COLON" {
			if tokens[offset+0].Type() == "NUMBER" {
				c.min, _ = strconv.ParseFloat(tokens[offset+0].String(), 64)
				if tokens[offset+2].Type() == "NUMBER" {
					c.max, _ = strconv.ParseFloat(tokens[offset+2].String(), 64)
					return c, nil
				} else if tokens[offset+2].Type() == "TILDE" {
					c.max = math.Inf(-1)
					return c, nil
				}
			} else if tokens[offset+0].Type() == "TILDE" {
				c.min = math.Inf(-1)
				if tokens[offset+2].Type() == "NUMBER" {
					c.max, _ = strconv.ParseFloat(tokens[offset+2].String(), 64)
					return c, nil
				} else if tokens[offset+2].Type() == "TILDE" {
					c.max = math.Inf(-1)
					return c, nil
				}
			}
		}
	} else if len(tokens)-offset == 2 {
		if tokens[offset+0].Type() == "COLON" {
			c.min = 0
			if tokens[offset+1].Type() == "NUMBER" {
				c.max, _ = strconv.ParseFloat(tokens[offset+1].String(), 64)
				return c, nil
			} else if tokens[offset+1].Type() == "TILDE" {
				c.max = math.Inf(-1)
				return c, nil
			}
		} else if tokens[offset+1].Type() == "COLON" {
			c.max = math.Inf(1)
			if tokens[offset+0].Type() == "NUMBER" {
				c.min, _ = strconv.ParseFloat(tokens[offset+0].String(), 64)
				return c, nil
			} else if tokens[offset+0].Type() == "TILDE" {
				c.min = math.Inf(-1)
				return c, nil
			}
		}
	} else if len(tokens)-offset == 1 {
		c.min = 0
		if tokens[offset+0].Type() == "NUMBER" {
			c.max, _ = strconv.ParseFloat(tokens[offset+0].String(), 64)
			return c, nil
		} else if tokens[offset+0].Type() == "TILDE" {
			c.max = math.Inf(-1)
			return c, nil
		}
	}
	return nil, ParseError
}
<|endoftext|>


<commit_before>package vagrantutil

import (
	"bufio"
	"strings"
)

// parseData parses the given vagrant type field from the machine readable
// output (records).
func (v *Vagrant) parseData(records [][]string, typeName string) (string, error) {
	data := ""
	for _, record := range records {
		// first three are defined, after that data is variadic, it contains
		// zero or more information. We should have a data, otherwise it's
		// useless.
		if len(record) < 4 {
			continue
		}

		if typeName == record[2] {
			data = record[3]
			if data != "" {
				break
			}
		}
	}

	if data == "" {
		return "", v.errorf("couldn't parse data for vagrant type: %q", typeName)
	}

	return data, nil
}

func (v *Vagrant) parseRecords(out string) (recs [][]string, err error) {
	scanner := bufio.NewScanner(strings.NewReader(out))

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		row := strings.Split(scanner.Text(), ",")
		recs = append(recs, row)
	}

	if err := scanner.Err(); err != nil {
		return nil, v.error(err)
	}

	return recs, nil
}
<commit_msg>vagrantutil: simplify parseData<commit_after>package vagrantutil

import (
	"bufio"
	"strings"
)

// parseData parses the given vagrant type field from the machine readable
// output (records).
func (v *Vagrant) parseData(records [][]string, typeName string) (string, error) {
	data := ""
	for _, record := range records {
		// first three are defined, after that data is variadic, it contains
		// zero or more information. We should have a data, otherwise it's
		// useless.
		if len(record) < 4 {
			continue
		}

		if typeName == record[2] && record[3] != "" {
			data = record[3]
			break
		}
	}

	if data == "" {
		return "", v.errorf("couldn't parse data for vagrant type: %q", typeName)
	}

	return data, nil
}

func (v *Vagrant) parseRecords(out string) (recs [][]string, err error) {
	scanner := bufio.NewScanner(strings.NewReader(out))

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		row := strings.Split(scanner.Text(), ",")
		recs = append(recs, row)
	}

	if err := scanner.Err(); err != nil {
		return nil, v.error(err)
	}

	return recs, nil
}
<|endoftext|>


<commit_before>// parse.go is purely experimental stuff, wanted to see performance of JSON
// parsing using parsec.

package collatejson

import (
	"fmt"
	"github.com/prataprc/golib/parsec"
	"io/ioutil"
)

type PropertyNode struct {
	propname string
	parsec.ParsecNode
}

var EMPTY = parsec.Terminal{Name: "EMPTY", Value: ""}

func ParseFile(filename string) parsec.ParsecNode {
	if text, err := ioutil.ReadFile(filename); err != nil {
		panic(err.Error())
	} else {
		return Parse(text)
	}
}

func Parse(text []byte) parsec.ParsecNode {
	s := parsec.NewScanner(text)
	nt, _ := y(*s)
	return nt
}

// Construct parser-combinator for parsing JSON string.
func y(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		return ns[0]
	}
	return parsec.Maybe(nodify, value)(s)
}

func array(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		return ns[1]
	}
	return parsec.And(nodify, opensqr, values, closesqr)(s)
}

func object(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		return ns[1]
	}
	return parsec.And(nodify, openparan, properties, closeparan)(s)
}

func properties(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		// Bubble sort properties based on property name.
		for i := 0; i < len(ns)-1; i++ {
			for j := 0; j < len(ns)-i-1; j++ {
				x := ns[j].(*PropertyNode).propname
				y := ns[j+1].(*PropertyNode).propname
				if x <= y {
					continue
				}
				ns[j+1], ns[j] = ns[j], ns[j+1]
			}
		}
		return &parsec.NonTerminal{Name: "PROPERTIES", Children: ns}
	}
	return parsec.Many(nodify, property, comma)(s)
}

func property(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		propname := ns[0].(*parsec.Terminal).Value
		return &PropertyNode{propname, ns[2]}
	}
	return parsec.And(nodify, parsec.String(), colon, value)(s)
}

func values(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		return &parsec.NonTerminal{Name: "VALUES", Children: ns}
	}
	return parsec.Many(nodify, value, comma)(s)
}

func value(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		return ns[0]
	}
	return parsec.OrdChoice(
		nodify, tRue, fAlse, null,
		parsec.String(), parsec.Float(), parsec.Int(),
		array, object,
	)(s)
}

var tRue = parsec.Token(`^true`, "TRUE")
var fAlse = parsec.Token(`^false`, "FALSE")
var null = parsec.Token(`^null`, "NULL")

var comma = parsec.Token(`^,`, "COMMA")
var colon = parsec.Token(`^:`, "COLON")
var opensqr = parsec.Token(`^\[`, "OPENSQR")
var closesqr = parsec.Token(`^\]`, "CLOSESQR")
var openparan = parsec.Token(`^\{`, "OPENPARAN")
var closeparan = parsec.Token(`^\}`, "CLOSEPARAN")

// INode APIs for Terminal
func Repr(tok parsec.ParsecNode, prefix string) string {
	if term, ok := tok.(*parsec.Terminal); ok {
		return fmt.Sprintf(prefix) +
			fmt.Sprintf("%v : %v ", term.Name, term.Value)
	} else if propterm, ok := tok.(*PropertyNode); ok {
		return fmt.Sprintf(prefix) +
			fmt.Sprintf("property : %v \n", propterm.propname)
	} else {
		nonterm, _ := tok.(*parsec.NonTerminal)
		return fmt.Sprintf(prefix) +
			fmt.Sprintf("%v : %v \n", nonterm.Name, nonterm.Value)
	}
	panic("invalid parsecNode")
}

func Show(tok parsec.ParsecNode, prefix string) {
	if term, ok := tok.(*parsec.Terminal); ok {
		fmt.Println(Repr(term, prefix))
	} else if propterm, ok := tok.(*PropertyNode); ok {
		fmt.Printf("%v", Repr(propterm, prefix))
		Show(propterm.ParsecNode, prefix+"  ")
	} else if nonterm, ok := tok.(*parsec.NonTerminal); ok {
		fmt.Printf("%v", Repr(nonterm, prefix))
		for _, tok := range nonterm.Children {
			Show(tok, prefix+"  ")
		}
	}
}
<commit_msg>parsec tool is moved to prataprc/parsec.<commit_after>// parse.go is purely experimental stuff, wanted to see performance of JSON
// parsing using parsec.

package collatejson

import (
	"fmt"
	"github.com/prataprc/parsec"
	"io/ioutil"
)

type PropertyNode struct {
	propname string
	parsec.ParsecNode
}

var EMPTY = parsec.Terminal{Name: "EMPTY", Value: ""}

func ParseFile(filename string) parsec.ParsecNode {
	if text, err := ioutil.ReadFile(filename); err != nil {
		panic(err.Error())
	} else {
		return Parse(text)
	}
}

func Parse(text []byte) parsec.ParsecNode {
	s := parsec.NewScanner(text)
	nt, _ := y(*s)
	return nt
}

// Construct parser-combinator for parsing JSON string.
func y(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		return ns[0]
	}
	return parsec.Maybe(nodify, value)(s)
}

func array(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		return ns[1]
	}
	return parsec.And(nodify, opensqr, values, closesqr)(s)
}

func object(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		return ns[1]
	}
	return parsec.And(nodify, openparan, properties, closeparan)(s)
}

func properties(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		// Bubble sort properties based on property name.
		for i := 0; i < len(ns)-1; i++ {
			for j := 0; j < len(ns)-i-1; j++ {
				x := ns[j].(*PropertyNode).propname
				y := ns[j+1].(*PropertyNode).propname
				if x <= y {
					continue
				}
				ns[j+1], ns[j] = ns[j], ns[j+1]
			}
		}
		return &parsec.NonTerminal{Name: "PROPERTIES", Children: ns}
	}
	return parsec.Many(nodify, property, comma)(s)
}

func property(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		propname := ns[0].(*parsec.Terminal).Value
		return &PropertyNode{propname, ns[2]}
	}
	return parsec.And(nodify, parsec.String(), colon, value)(s)
}

func values(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		return &parsec.NonTerminal{Name: "VALUES", Children: ns}
	}
	return parsec.Many(nodify, value, comma)(s)
}

func value(s parsec.Scanner) (parsec.ParsecNode, *parsec.Scanner) {
	nodify := func(ns []parsec.ParsecNode) parsec.ParsecNode {
		if ns == nil || len(ns) == 0 {
			return nil
		}
		return ns[0]
	}
	return parsec.OrdChoice(
		nodify, tRue, fAlse, null,
		parsec.String(), parsec.Float(), parsec.Int(),
		array, object,
	)(s)
}

var tRue = parsec.Token(`^true`, "TRUE")
var fAlse = parsec.Token(`^false`, "FALSE")
var null = parsec.Token(`^null`, "NULL")

var comma = parsec.Token(`^,`, "COMMA")
var colon = parsec.Token(`^:`, "COLON")
var opensqr = parsec.Token(`^\[`, "OPENSQR")
var closesqr = parsec.Token(`^\]`, "CLOSESQR")
var openparan = parsec.Token(`^\{`, "OPENPARAN")
var closeparan = parsec.Token(`^\}`, "CLOSEPARAN")

// INode APIs for Terminal
func Repr(tok parsec.ParsecNode, prefix string) string {
	if term, ok := tok.(*parsec.Terminal); ok {
		return fmt.Sprintf(prefix) +
			fmt.Sprintf("%v : %v ", term.Name, term.Value)
	} else if propterm, ok := tok.(*PropertyNode); ok {
		return fmt.Sprintf(prefix) +
			fmt.Sprintf("property : %v \n", propterm.propname)
	} else {
		nonterm, _ := tok.(*parsec.NonTerminal)
		return fmt.Sprintf(prefix) +
			fmt.Sprintf("%v : %v \n", nonterm.Name, nonterm.Value)
	}
	panic("invalid parsecNode")
}

func Show(tok parsec.ParsecNode, prefix string) {
	if term, ok := tok.(*parsec.Terminal); ok {
		fmt.Println(Repr(term, prefix))
	} else if propterm, ok := tok.(*PropertyNode); ok {
		fmt.Printf("%v", Repr(propterm, prefix))
		Show(propterm.ParsecNode, prefix+"  ")
	} else if nonterm, ok := tok.(*parsec.NonTerminal); ok {
		fmt.Printf("%v", Repr(nonterm, prefix))
		for _, tok := range nonterm.Children {
			Show(tok, prefix+"  ")
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package sh

import (
	"bufio"
	"fmt"
	"io"
	"regexp"
	"strconv"
)

const (
	_ = -iota
	EOF
	IDENT
	STRING
)

func parse(r io.Reader, name string) error {
	p := &parser{
		r:    bufio.NewReader(r),
		name: name,
		line: 1,
		col:  0,
	}
	p.program()
	return p.err
}

type parser struct {
	r    *bufio.Reader
	tok  int32
	err  error
	name string
	line int
	col  int
}

var reserved = map[rune]bool{
	'\n': true,
	'#':  true,
	'=':  true,
	'&':  true,
	'>':  true,
	'<':  true,
	'|':  true,
	';':  true,
	'(':  true,
	')':  true,
	'{':  true,
	'}':  true,
	'"':  true,
	'\'': true,
}

var space = map[rune]bool{
	' ':  true,
	'\t': true,
}

var ident = regexp.MustCompile(`^[a-zA-Z_]+[a-zA-Z0-9_]*$`)

func (p *parser) next() {
	if p.err != nil {
		return
	}
	r := ' '
	var err error
	for space[r] {
		r, _, err = p.r.ReadRune()
		if err == io.EOF {
			p.tok = EOF
			return
		}
		if err != nil {
			p.errPass(err)
			return
		}
		p.col++
	}
	if reserved[r] {
		if r == '\n' {
			p.line++
			p.col = 0
		}
		p.tok = r
		return
	}
	runes := []rune{r}
	for !reserved[r] && !space[r] {
		r, _, err = p.r.ReadRune()
		if err == io.EOF {
			break
		}
		if err != nil {
			p.errPass(err)
			return
		}
		p.col++
		runes = append(runes, r)
	}
	if len(runes) > 1 {
		if err := p.r.UnreadRune(); err != nil {
			p.errPass(err)
			return
		}
		p.col--
		runes = runes[:len(runes)-1]
	}
	p.tok = STRING
	s := string(runes)
	if ident.MatchString(s) {
		p.tok = IDENT
	}
	return
}

func (p *parser) discardUpTo(delim byte) {
	_, err := p.r.ReadBytes(delim)
	if err == io.EOF {
		p.tok = EOF
	} else if err != nil {
		p.errPass(err)
	}
}

func (p *parser) got(tok int32) bool {
	if p.tok == tok {
		p.next()
		return true
	}
	return false
}

func (p *parser) want(tok int32) {
	if p.tok != tok {
		p.errWanted(tok)
		return
	}
	p.next()
}

func tokStr(tok int32) string {
	switch tok {
	case EOF:
		return "EOF"
	case STRING:
		return "string"
	case IDENT:
		return "ident"
	default:
		return strconv.QuoteRune(tok)
	}
}

func (p *parser) errPass(err error) {
	p.err = err
	p.tok = EOF
}

func (p *parser) lineErr(format string, v ...interface{}) {
	pos := fmt.Sprintf("%s:%d:%d: ", p.name, p.line, p.col)
	p.errPass(fmt.Errorf(pos+format, v...))
}

func (p *parser) errUnexpected() {
	p.lineErr("unexpected token %s", tokStr(p.tok))
}

func (p *parser) errWanted(tok int32) {
	p.lineErr("unexpected token %s, wanted %s", tokStr(p.tok), tokStr(tok))
}

func (p *parser) program() {
	p.next()
	for p.tok != EOF {
		if p.got('\n') {
			continue
		}
		p.command()
	}
}

func (p *parser) command() {
	switch {
	case p.got('#'):
		p.discardUpTo('\n')
		p.next()
	case p.got('"'):
		p.strContent('"')
	case p.got('\''):
		p.strContent('\'')
	case p.got(IDENT):
		for p.tok != EOF {
			switch {
			case p.got(IDENT):
			case p.got(STRING):
			case p.got('='):
				switch {
				case p.got(IDENT):
				case p.got(STRING):
				}
			case p.got('&'):
				if p.got('&') {
					p.command()
				}
				return
			case p.got('|'):
				p.got('|')
				p.command()
				return
			case p.got('('):
				p.want(')')
				p.want('{')
				for !p.got('}') {
					if p.tok == EOF {
						p.errWanted('}')
						break
					}
					if p.got('\n') {
						continue
					}
					p.command()
				}
				return
			case p.got('>'):
				switch {
				case p.got('>'):
				case p.got('&'):
				}
				p.value()
			case p.got('<'):
				p.value()
			case p.got(';'):
				return
			case p.got('\n'):
				return
			default:
				p.errUnexpected()
			}
		}
	case p.got(STRING):
		for p.tok != EOF {
			switch {
			case p.got(IDENT):
			case p.got(STRING):
			case p.got('&'):
				if p.got('&') {
					p.command()
				}
				return
			case p.got('|'):
				p.got('|')
				p.command()
				return
			case p.got('>'):
				switch {
				case p.got('>'):
				case p.got('&'):
				}
				p.value()
			case p.got('<'):
				p.value()
			case p.got(';'):
				return
			case p.got('\n'):
				return
			default:
				p.errUnexpected()
			}
		}
	case p.got('{'):
		for p.tok != EOF && p.tok != '}' {
			if p.got('\n') {
				continue
			}
			p.command()
		}
		p.want('}')
		switch {
		case p.got('='):
			switch {
			case p.got(IDENT):
			case p.got(STRING):
			}
		case p.got('&'):
			if p.got('&') {
				p.command()
			}
			return
		case p.got('|'):
			p.got('|')
			p.command()
			return
		case p.got('('):
			p.want(')')
			p.want('{')
			for !p.got('}') {
				if p.tok == EOF {
					p.errWanted('}')
					break
				}
				if p.got('\n') {
					continue
				}
				p.command()
			}
			return
		case p.got('>'):
			switch {
			case p.got('>'):
			case p.got('&'):
			}
			p.value()
		case p.got('<'):
			p.value()
		case p.got(';'):
			return
		case p.got('\n'):
			return
		default:
			p.errUnexpected()
		}
	default:
		p.errUnexpected()
	}
}

func (p *parser) value() {
	switch {
	case p.got(IDENT):
	case p.got(STRING):
	default:
		p.errUnexpected()
	}
}

func (p *parser) strContent(delim byte) {
	_, err := p.r.ReadBytes(delim)
	if err != nil {
		p.errPass(err)
	}
}
<commit_msg>Blocks can't be assigned to<commit_after>// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package sh

import (
	"bufio"
	"fmt"
	"io"
	"regexp"
	"strconv"
)

const (
	_ = -iota
	EOF
	IDENT
	STRING
)

func parse(r io.Reader, name string) error {
	p := &parser{
		r:    bufio.NewReader(r),
		name: name,
		line: 1,
		col:  0,
	}
	p.program()
	return p.err
}

type parser struct {
	r    *bufio.Reader
	tok  int32
	err  error
	name string
	line int
	col  int
}

var reserved = map[rune]bool{
	'\n': true,
	'#':  true,
	'=':  true,
	'&':  true,
	'>':  true,
	'<':  true,
	'|':  true,
	';':  true,
	'(':  true,
	')':  true,
	'{':  true,
	'}':  true,
	'"':  true,
	'\'': true,
}

var space = map[rune]bool{
	' ':  true,
	'\t': true,
}

var ident = regexp.MustCompile(`^[a-zA-Z_]+[a-zA-Z0-9_]*$`)

func (p *parser) next() {
	if p.err != nil {
		return
	}
	r := ' '
	var err error
	for space[r] {
		r, _, err = p.r.ReadRune()
		if err == io.EOF {
			p.tok = EOF
			return
		}
		if err != nil {
			p.errPass(err)
			return
		}
		p.col++
	}
	if reserved[r] {
		if r == '\n' {
			p.line++
			p.col = 0
		}
		p.tok = r
		return
	}
	runes := []rune{r}
	for !reserved[r] && !space[r] {
		r, _, err = p.r.ReadRune()
		if err == io.EOF {
			break
		}
		if err != nil {
			p.errPass(err)
			return
		}
		p.col++
		runes = append(runes, r)
	}
	if len(runes) > 1 {
		if err := p.r.UnreadRune(); err != nil {
			p.errPass(err)
			return
		}
		p.col--
		runes = runes[:len(runes)-1]
	}
	p.tok = STRING
	s := string(runes)
	if ident.MatchString(s) {
		p.tok = IDENT
	}
	return
}

func (p *parser) discardUpTo(delim byte) {
	_, err := p.r.ReadBytes(delim)
	if err == io.EOF {
		p.tok = EOF
	} else if err != nil {
		p.errPass(err)
	}
}

func (p *parser) got(tok int32) bool {
	if p.tok == tok {
		p.next()
		return true
	}
	return false
}

func (p *parser) want(tok int32) {
	if p.tok != tok {
		p.errWanted(tok)
		return
	}
	p.next()
}

func tokStr(tok int32) string {
	switch tok {
	case EOF:
		return "EOF"
	case STRING:
		return "string"
	case IDENT:
		return "ident"
	default:
		return strconv.QuoteRune(tok)
	}
}

func (p *parser) errPass(err error) {
	p.err = err
	p.tok = EOF
}

func (p *parser) lineErr(format string, v ...interface{}) {
	pos := fmt.Sprintf("%s:%d:%d: ", p.name, p.line, p.col)
	p.errPass(fmt.Errorf(pos+format, v...))
}

func (p *parser) errUnexpected() {
	p.lineErr("unexpected token %s", tokStr(p.tok))
}

func (p *parser) errWanted(tok int32) {
	p.lineErr("unexpected token %s, wanted %s", tokStr(p.tok), tokStr(tok))
}

func (p *parser) program() {
	p.next()
	for p.tok != EOF {
		if p.got('\n') {
			continue
		}
		p.command()
	}
}

func (p *parser) command() {
	switch {
	case p.got('#'):
		p.discardUpTo('\n')
		p.next()
	case p.got('"'):
		p.strContent('"')
	case p.got('\''):
		p.strContent('\'')
	case p.got(IDENT):
		for p.tok != EOF {
			switch {
			case p.got(IDENT):
			case p.got(STRING):
			case p.got('='):
				switch {
				case p.got(IDENT):
				case p.got(STRING):
				}
			case p.got('&'):
				if p.got('&') {
					p.command()
				}
				return
			case p.got('|'):
				p.got('|')
				p.command()
				return
			case p.got('('):
				p.want(')')
				p.want('{')
				for !p.got('}') {
					if p.tok == EOF {
						p.errWanted('}')
						break
					}
					if p.got('\n') {
						continue
					}
					p.command()
				}
				return
			case p.got('>'):
				switch {
				case p.got('>'):
				case p.got('&'):
				}
				p.value()
			case p.got('<'):
				p.value()
			case p.got(';'):
				return
			case p.got('\n'):
				return
			default:
				p.errUnexpected()
			}
		}
	case p.got(STRING):
		for p.tok != EOF {
			switch {
			case p.got(IDENT):
			case p.got(STRING):
			case p.got('&'):
				if p.got('&') {
					p.command()
				}
				return
			case p.got('|'):
				p.got('|')
				p.command()
				return
			case p.got('>'):
				switch {
				case p.got('>'):
				case p.got('&'):
				}
				p.value()
			case p.got('<'):
				p.value()
			case p.got(';'):
				return
			case p.got('\n'):
				return
			default:
				p.errUnexpected()
			}
		}
	case p.got('{'):
		for p.tok != EOF && p.tok != '}' {
			if p.got('\n') {
				continue
			}
			p.command()
		}
		p.want('}')
		switch {
		case p.got('&'):
			if p.got('&') {
				p.command()
			}
			return
		case p.got('|'):
			p.got('|')
			p.command()
			return
		case p.got('('):
			p.want(')')
			p.want('{')
			for !p.got('}') {
				if p.tok == EOF {
					p.errWanted('}')
					break
				}
				if p.got('\n') {
					continue
				}
				p.command()
			}
			return
		case p.got('>'):
			switch {
			case p.got('>'):
			case p.got('&'):
			}
			p.value()
		case p.got('<'):
			p.value()
		case p.got(';'):
			return
		case p.got('\n'):
			return
		default:
			p.errUnexpected()
		}
	default:
		p.errUnexpected()
	}
}

func (p *parser) value() {
	switch {
	case p.got(IDENT):
	case p.got(STRING):
	default:
		p.errUnexpected()
	}
}

func (p *parser) strContent(delim byte) {
	_, err := p.r.ReadBytes(delim)
	if err != nil {
		p.errPass(err)
	}
}
<|endoftext|>