<commit_before>package docker

import "github.com/docker/docker/api/types/events"

const (
	//DefaultCapacity of a new EventLog.
	DefaultCapacity = 10
)

//EventLog keeps track of docker events. It has a limited capacity and
//behaves as a Circular Buffer - adding a new event removes the oldest one if
//the buffer is at its max capacity.
type EventLog struct {
	head     int // the most recent value written
	tail     int // the least recent value written
	capacity int
	messages []*events.Message
}

//NewEventLog creates an event log with the default capacity
func NewEventLog() *EventLog {
	log := &EventLog{}
	log.Init(DefaultCapacity)

	return log
}

//Capacity returns the capacity of the event log.
func (el *EventLog) Capacity() int {
	return el.capacity
}

//Count returns the number of events in the buffer
func (el *EventLog) Count() int {
	return el.tail - el.head
}

//Events returns a copy of the event buffer
func (el *EventLog) Events() []events.Message {
	if el.Count() == 0 {
		return nil
	}
	messages := make([]events.Message, el.Count())
	for i, message := range el.messages[el.head:el.tail] {
		messages[i] = *message
	}
	return messages
}

//Init sets the log in a working state. Must be
//called before doing any other operation
func (el *EventLog) Init(capacity int) {
	el.messages = make([]*events.Message, capacity, capacity*2)
	el.capacity = capacity
}

//Peek the latest event added
func (el *EventLog) Peek() *events.Message {
	return el.messages[el.tail-1]
}

//Push the given event to this log
func (el *EventLog) Push(message *events.Message) {
	// if the array is full, rewind
	if el.tail == el.capacity {
		el.rewind()
	}
	el.messages[el.tail] = message
	// check if the buffer is full,
	// and move head pointer appropriately
	if el.tail-el.head >= el.capacity {
		el.head++
	}
	el.tail++
}

func (el *EventLog) rewind() {
	l := len(el.messages)
	for i := 0; i < el.capacity-1; i++ {
		el.messages[i] = el.messages[l-el.capacity+i+1]
	}
	el.head, el.tail = 0, el.capacity-1
}
<commit_msg>Increase the number of Docker events stored<commit_after>package docker

import "github.com/docker/docker/api/types/events"

const (
	//DefaultCapacity of a new EventLog.
	DefaultCapacity = 50
)

//EventLog keeps track of docker events. It has a limited capacity and
//behaves as a Circular Buffer - adding a new event removes the oldest one if
//the buffer is at its max capacity.
type EventLog struct {
	head     int // the most recent value written
	tail     int // the least recent value written
	capacity int
	messages []*events.Message
}

//NewEventLog creates an event log with the default capacity
func NewEventLog() *EventLog {
	log := &EventLog{}
	log.Init(DefaultCapacity)

	return log
}

//Capacity returns the capacity of the event log.
func (el *EventLog) Capacity() int {
	return el.capacity
}

//Count returns the number of events in the buffer
func (el *EventLog) Count() int {
	return el.tail - el.head
}

//Events returns a copy of the event buffer
func (el *EventLog) Events() []events.Message {
	if el.Count() == 0 {
		return nil
	}
	messages := make([]events.Message, el.Count())
	for i, message := range el.messages[el.head:el.tail] {
		messages[i] = *message
	}
	return messages
}

//Init sets the log in a working state. Must be
//called before doing any other operation
func (el *EventLog) Init(capacity int) {
	el.messages = make([]*events.Message, capacity, capacity*2)
	el.capacity = capacity
}

//Peek the latest event added
func (el *EventLog) Peek() *events.Message {
	return el.messages[el.tail-1]
}

//Push the given event to this log
func (el *EventLog) Push(message *events.Message) {
	// if the array is full, rewind
	if el.tail == el.capacity {
		el.rewind()
	}
	el.messages[el.tail] = message
	// check if the buffer is full,
	// and move head pointer appropriately
	if el.tail-el.head >= el.capacity {
		el.head++
	}
	el.tail++
}

func (el *EventLog) rewind() {
	l := len(el.messages)
	for i := 0; i < el.capacity-1; i++ {
		el.messages[i] = el.messages[l-el.capacity+i+1]
	}
	el.head, el.tail = 0, el.capacity-1
}
<|endoftext|>


<commit_before>// Copyright 2015 go-swagger maintainers
//
// 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 generator

import (
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"

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

const (
	basicFixture = "../fixtures/petstores/petstore.json"
)

func testClientGenOpts() *GenOpts {
	g := &GenOpts{}
	g.Target = "."
	g.APIPackage = defaultAPIPackage
	g.ModelPackage = defaultModelPackage
	g.ServerPackage = defaultServerPackage
	g.ClientPackage = defaultClientPackage
	g.Principal = ""
	g.IncludeModel = true
	g.IncludeHandler = true
	g.IncludeParameters = true
	g.IncludeResponses = true
	g.IncludeSupport = true
	g.TemplateDir = ""
	g.DumpData = false
	g.IsClient = true
	if err := g.EnsureDefaults(); err != nil {
		panic(err)
	}
	return g
}

func Test_GenerateClient(t *testing.T) {
	log.SetOutput(ioutil.Discard)

	// exercise safeguards
	err := GenerateClient("test", []string{"model1"}, []string{"op1", "op2"}, nil)
	assert.Error(t, err)

	opts := testClientGenOpts()
	opts.TemplateDir = "dir/nowhere"
	err = GenerateClient("test", []string{"model1"}, []string{"op1", "op2"}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	opts.TemplateDir = "http://nowhere.com"
	err = GenerateClient("test", []string{"model1"}, []string{"op1", "op2"}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	opts.Spec = "dir/nowhere.yaml"
	err = GenerateClient("test", []string{"model1"}, []string{"op1", "op2"}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	opts.Spec = basicFixture
	err = GenerateClient("test", []string{"model1"}, []string{}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	// bad content in spec (HTML...)
	opts.Spec = "https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v2.0/json/petstore.json"
	err = GenerateClient("test", []string{}, []string{}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	// no operations selected
	opts.Spec = "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/yaml/petstore.yaml"
	err = GenerateClient("test", []string{}, []string{"wrongOperationID"}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	// generate remote spec
	opts.Spec = "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/yaml/petstore.yaml"
	cwd, _ := os.Getwd()
	tft, _ := ioutil.TempDir(cwd, "generated")
	defer func() {
		_ = os.RemoveAll(tft)
	}()
	opts.Target = tft
	opts.IsClient = true
	DefaultSectionOpts(opts)

	defer func() {
		_ = os.RemoveAll(opts.Target)
	}()
	err = GenerateClient("test", []string{}, []string{}, opts)
	assert.NoError(t, err)

	// just checks this does not fail
	origStdout := os.Stdout
	defer func() {
		os.Stdout = origStdout
	}()
	tgt, _ := ioutil.TempDir(cwd, "dumped")
	defer func() {
		_ = os.RemoveAll(tgt)
	}()
	os.Stdout, _ = os.Create(filepath.Join(tgt, "stdout"))
	opts.DumpData = true
	err = GenerateClient("test", []string{}, []string{}, opts)
	assert.NoError(t, err)
	_, err = os.Stat(filepath.Join(tgt, "stdout"))
	assert.NoError(t, err)
}

func assertImports(t testing.TB, baseImport, code string) {
	assertRegexpInCode(t, baseImport, code)
	assertRegexpInCode(t, `"`+baseImport+`/abc_linux"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/abc_linux"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/abc_test"`, code)
	assertRegexpInCode(t, `apiops\s+"`+baseImport+`/api"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/custom"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/hash_tag_donuts"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/nr123abc"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/nr_at_donuts"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/plus_donuts`, code)
	assertRegexpInCode(t, `strfmtops "`+baseImport+`/strfmt`, code)
	assertRegexpInCode(t, `"`+baseImport+`/forced`, code)
	assertRegexpInCode(t, `"`+baseImport+`/nr12nasty`, code)
	assertRegexpInCode(t, `"`+baseImport+`/override`, code)
	assertRegexpInCode(t, `"`+baseImport+`/gtl`, code)
	assertRegexpInCode(t, `"`+baseImport+`/operationsops`, code)
}

func TestClient(t *testing.T) {
	log.SetOutput(ioutil.Discard)

	base := os.Getenv("GOPATH")
	if base == "" {
		base = "."
	} else {
		base = filepath.Join(base, "src")
		err := os.MkdirAll(base, 0755)
		require.NoError(t, err)
	}
	targetdir, err := ioutil.TempDir(base, "swagger_nogo")
	require.NoError(t, err, "Failed to create a test target directory: %v", err)

	defer func() {
		_ = os.RemoveAll(targetdir)
		log.SetOutput(os.Stdout)
	}()

	tests := []struct {
		name      string
		spec      string
		template  string
		wantError bool
		prepare   func(opts *GenOpts)
		verify    func(testing.TB, string)
	}{
		{
			name:      "InvalidSpec",
			wantError: true,
			prepare: func(opts *GenOpts) {
				opts.Spec = invalidSpecExample
				opts.ValidateSpec = true
			},
		},
		{
			name: "BaseImportDisabled",
			prepare: func(opts *GenOpts) {
				opts.LanguageOpts.BaseImportFunc = nil
			},
			wantError: false,
		},
		{
			name:      "Non_existing_contributor_template",
			template:  "NonExistingContributorTemplate",
			wantError: true,
		},
		{
			name:      "Existing_contributor",
			template:  "stratoscale",
			wantError: false,
		},
		{
			name:      "packages mangling",
			wantError: false,
			spec:      filepath.Join("..", "fixtures", "bugs", "2111", "fixture-2111.yaml"),
			verify: func(t testing.TB, target string) {
				require.True(t, fileExists(target, "client"))

				// assert package generation based on mangled tags
				target = filepath.Join(target, "client")
				assert.True(t, fileExists(target, "abc_linux"))
				assert.True(t, fileExists(target, "abc_test"))
				assert.True(t, fileExists(target, "api"))
				assert.True(t, fileExists(target, "custom"))
				assert.True(t, fileExists(target, "hash_tag_donuts"))
				assert.True(t, fileExists(target, "nr123abc"))
				assert.True(t, fileExists(target, "nr_at_donuts"))
				assert.True(t, fileExists(target, "operations"))
				assert.True(t, fileExists(target, "plus_donuts"))
				assert.True(t, fileExists(target, "strfmt"))
				assert.True(t, fileExists(target, "forced"))
				assert.True(t, fileExists(target, "gtl"))
				assert.True(t, fileExists(target, "nr12nasty"))
				assert.True(t, fileExists(target, "override"))
				assert.True(t, fileExists(target, "operationsops"))

				buf, err := ioutil.ReadFile(filepath.Join(target, "foo_client.go"))
				require.NoError(t, err)

				// assert client import, with deconfliction
				code := string(buf)
				baseImport := `github.com/go-swagger/go-swagger/generator/swagger_nogo\d+/packages_mangling/client`
				assertImports(t, baseImport, code)

				assertInCode(t, `cli.Strfmt = strfmtops.New(transport, formats)`, code)
				assertInCode(t, `cli.API = apiops.New(transport, formats)`, code)
				assertInCode(t, `cli.Operations = operations.New(transport, formats)`, code)
			},
		},
		{
			name:      "packages flattening",
			wantError: false,
			spec:      filepath.Join("..", "fixtures", "bugs", "2111", "fixture-2111.yaml"),
			prepare: func(opts *GenOpts) {
				opts.SkipTagPackages = true
			},
			verify: func(t testing.TB, target string) {
				require.True(t, fileExists(target, "client"))

				// packages are not created here
				target = filepath.Join(target, "client")
				assert.False(t, fileExists(target, "abc_linux"))
				assert.False(t, fileExists(target, "abc_test"))
				assert.False(t, fileExists(target, "api"))
				assert.False(t, fileExists(target, "custom"))
				assert.False(t, fileExists(target, "hash_tag_donuts"))
				assert.False(t, fileExists(target, "nr123abc"))
				assert.False(t, fileExists(target, "nr_at_donuts"))
				assert.False(t, fileExists(target, "plus_donuts"))
				assert.False(t, fileExists(target, "strfmt"))
				assert.False(t, fileExists(target, "forced"))
				assert.False(t, fileExists(target, "gtl"))
				assert.False(t, fileExists(target, "nr12nasty"))
				assert.False(t, fileExists(target, "override"))
				assert.False(t, fileExists(target, "operationsops"))

				assert.True(t, fileExists(target, "operations"))
			},
		},
		{
			name:      "name with trailing API",
			spec:      filepath.Join("..", "fixtures", "bugs", "2278", "fixture-2278.yaml"),
			wantError: false,
		},
	}

	for i, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			opts := testClientGenOpts()
			opts.Spec = basicFixture
			opts.Target = filepath.Join(targetdir, opts.LanguageOpts.ManglePackageName(tt.name, "client_test"+strconv.Itoa(i)))
			err := os.MkdirAll(opts.Target, 0755)
			require.NoError(t, err)

			if tt.spec == "" {
				opts.Spec = basicFixture
			} else {
				opts.Spec = tt.spec
			}
			opts.Template = tt.template

			if tt.prepare != nil {
				tt.prepare(opts)
			}

			err = GenerateClient("foo", nil, nil, opts)
			if tt.wantError {
				require.Errorf(t, err, "expected an error for client build fixture: %s", opts.Spec)
			} else {
				require.NoError(t, err, "unexpected error for client build fixture: %s", opts.Spec)
			}

			if tt.verify != nil {
				tt.verify(t, opts.Target)
			}
		})
	}
}

func TestGenClient_1518(t *testing.T) {
	// test client response handling when unexpected success response kicks in
	log.SetOutput(ioutil.Discard)
	defer func() {
		log.SetOutput(os.Stdout)
	}()

	opts := testClientGenOpts()
	opts.Spec = filepath.Join("..", "fixtures", "bugs", "1518", "fixture-1518.yaml")

	cwd, _ := os.Getwd()
	tft, _ := ioutil.TempDir(cwd, "generated")
	opts.Target = tft

	defer func() {
		_ = os.RemoveAll(opts.Target)
	}()

	err := GenerateClient("client", []string{}, []string{}, opts)
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	fixtureConfig := map[string][]string{
		"client/operations/operations_client.go": { // generated file
			// expected code lines
			`success, ok := result.(*GetRecords1OK)`,
			`if ok {`,
			`return success, nil`,
			`msg := fmt.Sprintf(`,
			`panic(msg)`,
			// expected code lines
			`success, ok := result.(*GetRecords2OK)`,
			`if ok {`,
			`return success, nil`,
			`unexpectedSuccess := result.(*GetRecords2Default)`,
			`return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())`,
			// expected code lines
			`switch value := result.(type) {`,
			`case *GetRecords3OK:`,
			`return value, nil, nil`,
			`case *GetRecords3Created:`,
			`return nil, value, nil`,
			`msg := fmt.Sprintf(`,
			`panic(msg)`,
			// expected code lines
			`switch value := result.(type) {`,
			`case *GetRecords4OK:`,
			`return value, nil, nil`,
			`case *GetRecords4Created:`,
			`return nil, value, nil`,
			`unexpectedSuccess := result.(*GetRecords4Default)`,
			`return nil, nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())`,
		},
	}

	for fileToInspect, expectedCode := range fixtureConfig {
		code, err := ioutil.ReadFile(filepath.Join(opts.Target, filepath.FromSlash(fileToInspect)))
		require.NoError(t, err)
		for line, codeLine := range expectedCode {
			if !assertInCode(t, strings.TrimSpace(codeLine), string(code)) {
				t.Logf("Code expected did not match in codegenfile %s for expected line %d: %q", fileToInspect, line, expectedCode[line])
			}
		}
	}
}
<commit_msg>fix tests on CI<commit_after>// Copyright 2015 go-swagger maintainers
//
// 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 generator

import (
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"

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

const (
	basicFixture = "../fixtures/petstores/petstore.json"
)

func testClientGenOpts() *GenOpts {
	g := &GenOpts{}
	g.Target = "."
	g.APIPackage = defaultAPIPackage
	g.ModelPackage = defaultModelPackage
	g.ServerPackage = defaultServerPackage
	g.ClientPackage = defaultClientPackage
	g.Principal = ""
	g.IncludeModel = true
	g.IncludeHandler = true
	g.IncludeParameters = true
	g.IncludeResponses = true
	g.IncludeSupport = true
	g.TemplateDir = ""
	g.DumpData = false
	g.IsClient = true
	if err := g.EnsureDefaults(); err != nil {
		panic(err)
	}
	return g
}

func Test_GenerateClient(t *testing.T) {
	log.SetOutput(ioutil.Discard)

	// exercise safeguards
	err := GenerateClient("test", []string{"model1"}, []string{"op1", "op2"}, nil)
	assert.Error(t, err)

	opts := testClientGenOpts()
	opts.TemplateDir = "dir/nowhere"
	err = GenerateClient("test", []string{"model1"}, []string{"op1", "op2"}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	opts.TemplateDir = "http://nowhere.com"
	err = GenerateClient("test", []string{"model1"}, []string{"op1", "op2"}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	opts.Spec = "dir/nowhere.yaml"
	err = GenerateClient("test", []string{"model1"}, []string{"op1", "op2"}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	opts.Spec = basicFixture
	err = GenerateClient("test", []string{"model1"}, []string{}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	// bad content in spec (HTML...)
	opts.Spec = "https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v2.0/json/petstore.json"
	err = GenerateClient("test", []string{}, []string{}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	// no operations selected
	opts.Spec = "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/yaml/petstore.yaml"
	err = GenerateClient("test", []string{}, []string{"wrongOperationID"}, opts)
	assert.Error(t, err)

	opts = testClientGenOpts()
	// generate remote spec
	opts.Spec = "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/yaml/petstore.yaml"
	cwd, _ := os.Getwd()
	tft, _ := ioutil.TempDir(cwd, "generated")
	defer func() {
		_ = os.RemoveAll(tft)
	}()
	opts.Target = tft
	opts.IsClient = true
	DefaultSectionOpts(opts)

	defer func() {
		_ = os.RemoveAll(opts.Target)
	}()
	err = GenerateClient("test", []string{}, []string{}, opts)
	assert.NoError(t, err)

	// just checks this does not fail
	origStdout := os.Stdout
	defer func() {
		os.Stdout = origStdout
	}()
	tgt, _ := ioutil.TempDir(cwd, "dumped")
	defer func() {
		_ = os.RemoveAll(tgt)
	}()
	os.Stdout, _ = os.Create(filepath.Join(tgt, "stdout"))
	opts.DumpData = true
	err = GenerateClient("test", []string{}, []string{}, opts)
	assert.NoError(t, err)
	_, err = os.Stat(filepath.Join(tgt, "stdout"))
	assert.NoError(t, err)
}

func assertImports(t testing.TB, baseImport, code string) {
	assertRegexpInCode(t, baseImport, code)
	assertRegexpInCode(t, `"`+baseImport+`/abc_linux"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/abc_linux"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/abc_test"`, code)
	assertRegexpInCode(t, `apiops\s+"`+baseImport+`/api"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/custom"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/hash_tag_donuts"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/nr123abc"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/nr_at_donuts"`, code)
	assertRegexpInCode(t, `"`+baseImport+`/plus_donuts`, code)
	assertRegexpInCode(t, `strfmtops "`+baseImport+`/strfmt`, code)
	assertRegexpInCode(t, `"`+baseImport+`/forced`, code)
	assertRegexpInCode(t, `"`+baseImport+`/nr12nasty`, code)
	assertRegexpInCode(t, `"`+baseImport+`/override`, code)
	assertRegexpInCode(t, `"`+baseImport+`/gtl`, code)
	assertRegexpInCode(t, `"`+baseImport+`/operationsops`, code)
}

func TestClient(t *testing.T) {
	log.SetOutput(ioutil.Discard)

	base := os.Getenv("GOPATH")
	if base == "" {
		base = "."
	} else {
		base = filepath.Join(base, "src")
		err := os.MkdirAll(base, 0755)
		require.NoError(t, err)
	}
	targetdir, err := ioutil.TempDir(base, "swagger_nogo")
	require.NoError(t, err, "Failed to create a test target directory: %v", err)

	defer func() {
		_ = os.RemoveAll(targetdir)
		log.SetOutput(os.Stdout)
	}()

	tests := []struct {
		name      string
		spec      string
		template  string
		wantError bool
		prepare   func(opts *GenOpts)
		verify    func(testing.TB, string)
	}{
		{
			name:      "InvalidSpec",
			wantError: true,
			prepare: func(opts *GenOpts) {
				opts.Spec = invalidSpecExample
				opts.ValidateSpec = true
			},
		},
		{
			name: "BaseImportDisabled",
			prepare: func(opts *GenOpts) {
				opts.LanguageOpts.BaseImportFunc = nil
			},
			wantError: false,
		},
		{
			name:      "Non_existing_contributor_template",
			template:  "NonExistingContributorTemplate",
			wantError: true,
		},
		{
			name:      "Existing_contributor",
			template:  "stratoscale",
			wantError: false,
		},
		{
			name:      "packages mangling",
			wantError: false,
			spec:      filepath.Join("..", "fixtures", "bugs", "2111", "fixture-2111.yaml"),
			verify: func(t testing.TB, target string) {
				require.True(t, fileExists(target, "client"))

				// assert package generation based on mangled tags
				target = filepath.Join(target, "client")
				assert.True(t, fileExists(target, "abc_linux"))
				assert.True(t, fileExists(target, "abc_test"))
				assert.True(t, fileExists(target, "api"))
				assert.True(t, fileExists(target, "custom"))
				assert.True(t, fileExists(target, "hash_tag_donuts"))
				assert.True(t, fileExists(target, "nr123abc"))
				assert.True(t, fileExists(target, "nr_at_donuts"))
				assert.True(t, fileExists(target, "operations"))
				assert.True(t, fileExists(target, "plus_donuts"))
				assert.True(t, fileExists(target, "strfmt"))
				assert.True(t, fileExists(target, "forced"))
				assert.True(t, fileExists(target, "gtl"))
				assert.True(t, fileExists(target, "nr12nasty"))
				assert.True(t, fileExists(target, "override"))
				assert.True(t, fileExists(target, "operationsops"))

				buf, err := ioutil.ReadFile(filepath.Join(target, "foo_client.go"))
				require.NoError(t, err)

				// assert client import, with deconfliction
				code := string(buf)
				baseImport := `swagger_nogo\d+/packages_mangling/client`
				assertImports(t, baseImport, code)

				assertInCode(t, `cli.Strfmt = strfmtops.New(transport, formats)`, code)
				assertInCode(t, `cli.API = apiops.New(transport, formats)`, code)
				assertInCode(t, `cli.Operations = operations.New(transport, formats)`, code)
			},
		},
		{
			name:      "packages flattening",
			wantError: false,
			spec:      filepath.Join("..", "fixtures", "bugs", "2111", "fixture-2111.yaml"),
			prepare: func(opts *GenOpts) {
				opts.SkipTagPackages = true
			},
			verify: func(t testing.TB, target string) {
				require.True(t, fileExists(target, "client"))

				// packages are not created here
				target = filepath.Join(target, "client")
				assert.False(t, fileExists(target, "abc_linux"))
				assert.False(t, fileExists(target, "abc_test"))
				assert.False(t, fileExists(target, "api"))
				assert.False(t, fileExists(target, "custom"))
				assert.False(t, fileExists(target, "hash_tag_donuts"))
				assert.False(t, fileExists(target, "nr123abc"))
				assert.False(t, fileExists(target, "nr_at_donuts"))
				assert.False(t, fileExists(target, "plus_donuts"))
				assert.False(t, fileExists(target, "strfmt"))
				assert.False(t, fileExists(target, "forced"))
				assert.False(t, fileExists(target, "gtl"))
				assert.False(t, fileExists(target, "nr12nasty"))
				assert.False(t, fileExists(target, "override"))
				assert.False(t, fileExists(target, "operationsops"))

				assert.True(t, fileExists(target, "operations"))
			},
		},
		{
			name:      "name with trailing API",
			spec:      filepath.Join("..", "fixtures", "bugs", "2278", "fixture-2278.yaml"),
			wantError: false,
		},
	}

	for i, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			opts := testClientGenOpts()
			opts.Spec = basicFixture
			opts.Target = filepath.Join(targetdir, opts.LanguageOpts.ManglePackageName(tt.name, "client_test"+strconv.Itoa(i)))
			err := os.MkdirAll(opts.Target, 0755)
			require.NoError(t, err)

			if tt.spec == "" {
				opts.Spec = basicFixture
			} else {
				opts.Spec = tt.spec
			}
			opts.Template = tt.template

			if tt.prepare != nil {
				tt.prepare(opts)
			}

			err = GenerateClient("foo", nil, nil, opts)
			if tt.wantError {
				require.Errorf(t, err, "expected an error for client build fixture: %s", opts.Spec)
			} else {
				require.NoError(t, err, "unexpected error for client build fixture: %s", opts.Spec)
			}

			if tt.verify != nil {
				tt.verify(t, opts.Target)
			}
		})
	}
}

func TestGenClient_1518(t *testing.T) {
	// test client response handling when unexpected success response kicks in
	log.SetOutput(ioutil.Discard)
	defer func() {
		log.SetOutput(os.Stdout)
	}()

	opts := testClientGenOpts()
	opts.Spec = filepath.Join("..", "fixtures", "bugs", "1518", "fixture-1518.yaml")

	cwd, _ := os.Getwd()
	tft, _ := ioutil.TempDir(cwd, "generated")
	opts.Target = tft

	defer func() {
		_ = os.RemoveAll(opts.Target)
	}()

	err := GenerateClient("client", []string{}, []string{}, opts)
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	fixtureConfig := map[string][]string{
		"client/operations/operations_client.go": { // generated file
			// expected code lines
			`success, ok := result.(*GetRecords1OK)`,
			`if ok {`,
			`return success, nil`,
			`msg := fmt.Sprintf(`,
			`panic(msg)`,
			// expected code lines
			`success, ok := result.(*GetRecords2OK)`,
			`if ok {`,
			`return success, nil`,
			`unexpectedSuccess := result.(*GetRecords2Default)`,
			`return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())`,
			// expected code lines
			`switch value := result.(type) {`,
			`case *GetRecords3OK:`,
			`return value, nil, nil`,
			`case *GetRecords3Created:`,
			`return nil, value, nil`,
			`msg := fmt.Sprintf(`,
			`panic(msg)`,
			// expected code lines
			`switch value := result.(type) {`,
			`case *GetRecords4OK:`,
			`return value, nil, nil`,
			`case *GetRecords4Created:`,
			`return nil, value, nil`,
			`unexpectedSuccess := result.(*GetRecords4Default)`,
			`return nil, nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())`,
		},
	}

	for fileToInspect, expectedCode := range fixtureConfig {
		code, err := ioutil.ReadFile(filepath.Join(opts.Target, filepath.FromSlash(fileToInspect)))
		require.NoError(t, err)
		for line, codeLine := range expectedCode {
			if !assertInCode(t, strings.TrimSpace(codeLine), string(code)) {
				t.Logf("Code expected did not match in codegenfile %s for expected line %d: %q", fileToInspect, line, expectedCode[line])
			}
		}
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Fabrício Godoy
 *
 * 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 docker_test

import (
	"bufio"
	"fmt"
	"net"
	"time"

	"github.com/skarllot/raiqub/docker"
)

func ExampleRedisDocker() {
	var config = struct {
		port      uint16
		image     string
		container string
		timeout   time.Duration
	}{
		6379,
		"redis",
		"redis-example",
		1 * time.Minute,
	}

	image := docker.NewImage(docker.NewDocker(), config.image)
	if err := image.Setup(); err != nil {
		// Ignore test compliance when Docker is not installed.
		fmt.Println("Container created")
		fmt.Println("Connected to Redis server")
		fmt.Println("+OK")
		fmt.Println("$5")
		fmt.Println("world")
		return
	}

	redis := docker.NewContainerTemplate(config.container, config.port)
	if err := image.Run(redis); err != nil {
		fmt.Println("Error trying to create a container:", err)
		return
	}
	defer redis.Remove()
	defer redis.Kill()
	fmt.Println("Container created")

	if err := redis.WaitStartup(config.timeout); err != nil {
		fmt.Println("Timeout waiting for Redis instance to respond")
		return
	}

	var ip string
	var err error
	if ip, err = redis.IP(); err != nil {
		fmt.Println("Error trying to get instance IP:", err)
		return
	}

	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, config.port))
	if err != nil {
		fmt.Println("Could not connect to Redis server:", err)
		return
	}
	defer conn.Close()
	fmt.Println("Connected to Redis server")

	reader := bufio.NewReader(conn)
	fmt.Fprintln(conn, "SET hello world")
	out, _ := reader.ReadString('\n')
	fmt.Println(out[:3])

	fmt.Fprintln(conn, "GET hello")
	out, _ = reader.ReadString('\n')
	fmt.Println(out[:2])
	out, _ = reader.ReadString('\n')
	fmt.Println(out[:5])

	// Output:
	// Container created
	// Connected to Redis server
	// +OK
	// $5
	// world
}
<commit_msg>Fixed Docker Redis example<commit_after>/*
 * Copyright 2015 Fabrício Godoy
 *
 * 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 docker_test

import (
	"bufio"
	"fmt"
	"net"
	"time"

	"github.com/skarllot/raiqub/docker"
)

func Example() {
	var config = struct {
		port      uint16
		image     string
		container string
		timeout   time.Duration
	}{
		6379,
		"redis",
		"redis-example",
		1 * time.Minute,
	}

	image := docker.NewImage(docker.NewDocker(), config.image)
	if err := image.Setup(); err != nil {
		// Ignore test compliance when Docker is not installed.
		fmt.Println("Container created")
		fmt.Println("Connected to Redis server")
		fmt.Println("+OK")
		fmt.Println("$5")
		fmt.Println("world")
		return
	}

	redis := docker.NewContainerTemplate(config.container, config.port)
	if err := image.Run(redis); err != nil {
		fmt.Println("Error trying to create a container:", err)
		return
	}
	defer redis.Remove()
	defer redis.Kill()
	fmt.Println("Container created")

	if err := redis.WaitStartup(config.timeout); err != nil {
		fmt.Println("Timeout waiting for Redis instance to respond")
		return
	}

	var ip string
	var err error
	if ip, err = redis.IP(); err != nil {
		fmt.Println("Error trying to get instance IP:", err)
		return
	}

	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, config.port))
	if err != nil {
		fmt.Println("Could not connect to Redis server:", err)
		return
	}
	defer conn.Close()
	fmt.Println("Connected to Redis server")

	reader := bufio.NewReader(conn)
	fmt.Fprintln(conn, "SET hello world")
	out, _ := reader.ReadString('\n')
	fmt.Println(out[:3])

	fmt.Fprintln(conn, "GET hello")
	out, _ = reader.ReadString('\n')
	fmt.Println(out[:2])
	out, _ = reader.ReadString('\n')
	fmt.Println(out[:5])

	// Output:
	// Container created
	// Connected to Redis server
	// +OK
	// $5
	// world
}
<|endoftext|>


<commit_before>// Package maildir implements reading and writing maildir directories as specified in http://cr.yp.to/proto/maildir.html.
package maildir

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/mail"
	"os"
	"path"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

const (
	cur = "cur"
	tmp = "tmp"
	nw  = "new"
)

var (
	pid      int
	cntr     uint64
	hostname string
)

func init() {
	pid = os.Getpid()
	h, _ := os.Hostname()
	hostname = strings.Replace(strings.Replace(h, "/", "\057", -1), ":", "\072", -1)
}

// Key is a key of a maildir message.
type Key string

type Maildir struct {
	dir string
}

// Create creates a maildir rooted at dir.
func Create(dir string) (Maildir, error) {
	m := Maildir{dir}
	for _, x := range []string{cur, tmp, nw} {
		if err := os.MkdirAll(path.Join(dir, x), 0766); err != nil {
			return m, err
		}
	}
	return m, nil
}

// Deliver delivers the Message to the "new" maildir.
func (d Maildir) Deliver(m *mail.Message) (Key, error) {
	k := strconv.FormatInt(time.Now().Unix(), 10) + "."
	k += strconv.FormatInt(int64(pid), 10) + "_" + strconv.FormatUint(atomic.AddUint64(&cntr, 1), 10)
	k += "." + hostname
	key := Key(k)
	f, err := os.Create(path.Join(d.dir, tmp, k))
	if err != nil {
		return key, err
	}
	defer f.Close()
	for h, vs := range m.Header {
		for _, v := range vs {
			if _, err := f.WriteString(h + ": " + v + "\n"); err != nil {
				return key, err
			}
		}
	}
	if _, err := f.WriteString("\r\n"); err != nil {
		return key, err
	}
	if _, err := io.Copy(f, m.Body); err != nil {
		return key, err
	}
	return key, os.Rename(path.Join(d.dir, tmp, k), path.Join(d.dir, nw, k))
}

// GetFile gets the file path for the specified key.
func (d Maildir) GetFile(k Key) (string, error) {
	// Check in new.
	f := path.Join(d.dir, nw, string(k))
	if _, err := os.Stat(f); err == nil {
		return f, nil
	}
	// Check in cur.
	fs, err := ioutil.ReadDir(path.Join(d.dir, cur))
	if err != nil {
		return "", err
	}
	for _, f := range fs {
		if strings.HasPrefix(f.Name(), string(k)+":") {
			return path.Join(d.dir, cur, f.Name()), nil
		}
	}
	return "", fmt.Errorf("Does not exist")
}

// Delete removes the message with the specified key from cur/new.
func (d Maildir) Delete(k Key) error {
	f, err := d.GetFile(k)
	if err != nil {
		return err
	}
	return os.Remove(f)
}
<commit_msg>Add missing docstring.<commit_after>// Package maildir implements reading and writing maildir directories as specified in http://cr.yp.to/proto/maildir.html.
package maildir

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/mail"
	"os"
	"path"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

const (
	cur = "cur"
	tmp = "tmp"
	nw  = "new"
)

var (
	pid      int
	cntr     uint64
	hostname string
)

func init() {
	pid = os.Getpid()
	h, _ := os.Hostname()
	hostname = strings.Replace(strings.Replace(h, "/", "\057", -1), ":", "\072", -1)
}

// Key is a key of a maildir message.
type Key string

// Maildir is a single maildir directory.
type Maildir struct {
	dir string
}

// Create creates a maildir rooted at dir.
func Create(dir string) (Maildir, error) {
	m := Maildir{dir}
	for _, x := range []string{cur, tmp, nw} {
		if err := os.MkdirAll(path.Join(dir, x), 0766); err != nil {
			return m, err
		}
	}
	return m, nil
}

// Deliver delivers the Message to the "new" maildir.
func (d Maildir) Deliver(m *mail.Message) (Key, error) {
	k := strconv.FormatInt(time.Now().Unix(), 10) + "."
	k += strconv.FormatInt(int64(pid), 10) + "_" + strconv.FormatUint(atomic.AddUint64(&cntr, 1), 10)
	k += "." + hostname
	key := Key(k)
	f, err := os.Create(path.Join(d.dir, tmp, k))
	if err != nil {
		return key, err
	}
	defer f.Close()
	for h, vs := range m.Header {
		for _, v := range vs {
			if _, err := f.WriteString(h + ": " + v + "\n"); err != nil {
				return key, err
			}
		}
	}
	if _, err := f.WriteString("\r\n"); err != nil {
		return key, err
	}
	if _, err := io.Copy(f, m.Body); err != nil {
		return key, err
	}
	return key, os.Rename(path.Join(d.dir, tmp, k), path.Join(d.dir, nw, k))
}

// GetFile gets the file path for the specified key.
func (d Maildir) GetFile(k Key) (string, error) {
	// Check in new.
	f := path.Join(d.dir, nw, string(k))
	if _, err := os.Stat(f); err == nil {
		return f, nil
	}
	// Check in cur.
	fs, err := ioutil.ReadDir(path.Join(d.dir, cur))
	if err != nil {
		return "", err
	}
	for _, f := range fs {
		if strings.HasPrefix(f.Name(), string(k)+":") {
			return path.Join(d.dir, cur, f.Name()), nil
		}
	}
	return "", fmt.Errorf("Does not exist")
}

// Delete removes the message with the specified key from cur/new.
func (d Maildir) Delete(k Key) error {
	f, err := d.GetFile(k)
	if err != nil {
		return err
	}
	return os.Remove(f)
}
<|endoftext|>


<commit_before>package websocket_test

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"testing"

	gorilla "github.com/gorilla/websocket"
	"github.com/spring1843/chat-server/src/chat"
	"github.com/spring1843/chat-server/src/config"
	"github.com/spring1843/chat-server/src/drivers/websocket"
)

func TestCantStartTwoUsers(t *testing.T) {
	config := config.Config{
		WebAddress: "127.0.0.1:4008",
	}

	chatServer := chat.NewServer()
	chatServer.Listen()
	websocket.SetWebSocket(chatServer)

	http.HandleFunc("/ws1", websocket.Handler)

	go func() {
		if err := http.ListenAndServe(config.WebAddress, nil); err != nil {
			t.Fatalf("Failed listening to WebSocket on %s. Error %s.", config.WebAddress, err)
		}
	}()

	tryouts := 2
	conns := make([]*gorilla.Conn, tryouts, tryouts)
	i := 0
	for i < tryouts {
		nickName := fmt.Sprintf("user%d", i)
		conns[i] = connectUser(t, nickName, "/ws1", config)
		i++
	}

	if chatServer.ConnectedUsersCount() != tryouts {
		t.Fatalf("Expected user count to be %d after disconnecting users, got %d", tryouts, chatServer.ConnectedUsersCount())
	}

	i = 0
	for i < tryouts {
		disconnectUser(t, conns[i], chatServer)
		i++
	}

	if chatServer.ConnectedUsersCount() != 0 {
		t.Fatalf("Expected user count to be %d after disconnecting users, got %d", 0, chatServer.ConnectedUsersCount())
	}
}

func TestCantStartAndConnectManyUsers(t *testing.T) {
	config := config.Config{
		WebAddress: "127.0.0.1:4009",
	}

	chatServer := chat.NewServer()
	chatServer.Listen()
	websocket.SetWebSocket(chatServer)

	http.HandleFunc("/ws2", websocket.Handler)

	go func() {
		if err := http.ListenAndServe(config.WebAddress, nil); err != nil {
			t.Fatalf("Failed listening to WebSocket on %s. Error %s.", config.WebAddress, err)
		}
	}()

	tryouts := 100
	i := 0
	for i < tryouts {
		nickName := fmt.Sprintf("user%d", i)
		go connectAndDisconnect(t, nickName, "/ws2", config, chatServer)
		i++
	}
}

func connectUser(t *testing.T, nickname string, wsPath string, config config.Config) *gorilla.Conn {
	url := url.URL{Scheme: "ws", Host: config.WebAddress, Path: wsPath}

	conn, _, err := gorilla.DefaultDialer.Dial(url.String(), nil)
	if err != nil {
		log.Fatalf("Websocket Dial error: %s", err.Error())
	}

	_, message, err := conn.ReadMessage()
	if err != nil {
		log.Fatalf("Error while reading connection %s", err.Error())
	}

	if !strings.Contains(string(message), "Welcome") {
		t.Error("Could not receive welcome message")
	}

	if err := conn.WriteMessage(1, []byte(nickname)); err != nil {
		log.Fatalf("Error writing to connection. Error %s", err)
	}

	_, message, err = conn.ReadMessage()
	if err != nil {
		log.Fatalf("Error while reading connection. Error %s", err.Error())
	}

	expect := "Welcome " + nickname
	if !strings.Contains(string(message), expect) {
		log.Fatalf("Could not set user %s, expected 'Thanks User1' got %s", nickname, expect)
	}

	return conn
}

func joinChannel(t *testing.T, conn *gorilla.Conn) {
	if err := conn.WriteMessage(1, []byte("/join #r")); err != nil {
		log.Fatalf("Error writing to connection. Error %s", err)
	}

	_, message, err := conn.ReadMessage()
	if err != nil {
		log.Fatalf("Error while reading connection. Error %s", err.Error())
	}

	expect := "setChannel"
	expect2 := "You are now in #r"
	if !strings.Contains(string(message), expect) && !strings.Contains(string(message), expect2) {
		log.Fatalf("Could not join channel #r. Expected %q or %q got %q", expect, expect2, message)
	}
}

func disconnectUser(t *testing.T, conn *gorilla.Conn, chatServer *chat.Server) {
	if err := conn.WriteMessage(1, []byte(`/quit`)); err != nil {
		log.Fatalf("Error writing to connection. Error %s", err)
	}

	_, message, err := conn.ReadMessage()
	if err != nil {
		log.Fatalf("Failed reading from WebSocket connection. Error %s", err)
	}
	if !strings.Contains(string(message), "Good Bye") {
		log.Fatalf("Could not quit from server. Expected 'Good Bye' got %s", string(message))
	}

	if chatServer.IsUserConnected("User1") {
		log.Fatal("User is still connected to server after quiting")
	}
}

func connectAndDisconnect(t *testing.T, nickname string, wsPath string, config config.Config, chatServer *chat.Server) {
	conn := connectUser(t, nickname, wsPath, config)
	joinChannel(t, conn)
	disconnectUser(t, conn, chatServer)
}
<commit_msg>Tests run<commit_after>package websocket_test

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"testing"

	gorilla "github.com/gorilla/websocket"
	"github.com/spring1843/chat-server/src/chat"
	"github.com/spring1843/chat-server/src/config"
	"github.com/spring1843/chat-server/src/drivers/websocket"
)

func TestCantStartTwoUsers(t *testing.T) {
	config := config.Config{
		WebAddress: "127.0.0.1:4008",
	}

	chatServer := chat.NewServer()
	chatServer.Listen()
	websocket.SetWebSocket(chatServer)

	http.HandleFunc("/ws1", websocket.Handler)

	go func() {
		if err := http.ListenAndServe(config.WebAddress, nil); err != nil {
			t.Fatalf("Failed listening to WebSocket on %s. Error %s.", config.WebAddress, err)
		}
	}()

	tryouts := 2
	conns := make([]*gorilla.Conn, tryouts, tryouts)
	i := 0
	for i < tryouts {
		nickName := fmt.Sprintf("user%d", i)
		conns[i] = connectUser(nickName, "/ws1", config)
		i++
	}

	if chatServer.ConnectedUsersCount() != tryouts {
		t.Fatalf("Expected user count to be %d after disconnecting users, got %d", tryouts, chatServer.ConnectedUsersCount())
	}

	i = 0
	for i < tryouts {
		disconnectUser(conns[i], chatServer)
		i++
	}

	if chatServer.ConnectedUsersCount() != 0 {
		t.Fatalf("Expected user count to be %d after disconnecting users, got %d", 0, chatServer.ConnectedUsersCount())
	}
}

func TestCantStartAndConnectManyUsers(t *testing.T) {
	config := config.Config{
		WebAddress: "127.0.0.1:4009",
	}

	chatServer := chat.NewServer()
	chatServer.Listen()
	websocket.SetWebSocket(chatServer)

	http.HandleFunc("/ws2", websocket.Handler)

	go func() {
		if err := http.ListenAndServe(config.WebAddress, nil); err != nil {
			t.Fatalf("Failed listening to WebSocket on %s. Error %s.", config.WebAddress, err)
		}
	}()

	tryouts := 100
	i := 0
	for i < tryouts {
		nickName := fmt.Sprintf("user%d", i)
		go connectAndDisconnect(t, nickName, "/ws2", config, chatServer)
		i++
	}
}

func connectUser(nickname string, wsPath string, config config.Config) *gorilla.Conn {
	url := url.URL{Scheme: "ws", Host: config.WebAddress, Path: wsPath}

	conn, _, err := gorilla.DefaultDialer.Dial(url.String(), nil)
	if err != nil {
		log.Fatalf("Websocket Dial error: %s", err.Error())
	}

	_, message, err := conn.ReadMessage()
	if err != nil {
		log.Fatalf("Error while reading connection %s", err.Error())
	}

	if !strings.Contains(string(message), "Welcome") {
		log.Fatalf("Could not receive welcome message. In %s", message)
	}

	if err := conn.WriteMessage(1, []byte(nickname)); err != nil {
		log.Fatalf("Error writing to connection. Error %s", err)
	}

	_, message, err = conn.ReadMessage()
	if err != nil {
		log.Fatalf("Error while reading connection. Error %s", err.Error())
	}

	expect := "Welcome " + nickname
	if !strings.Contains(string(message), expect) {
		log.Fatalf("Could not set user %s, expected 'Thanks User1' got %s", nickname, expect)
	}

	return conn
}

func joinChannel(conn *gorilla.Conn) {
	if err := conn.WriteMessage(1, []byte("/join #r")); err != nil {
		log.Fatalf("Error writing to connection. Error %s", err)
	}

	_, message, err := conn.ReadMessage()
	if err != nil {
		log.Fatalf("Error while reading connection. Error %s", err.Error())
	}
	expect := "05"
	if !strings.Contains(string(message), expect) {
		log.Fatalf("Could not join channel #r. Expected %q got %q", expect, message)
	}

	_, message, err = conn.ReadMessage()
	if err != nil {
		log.Fatalf("Error while reading connection. Error %s", err.Error())
	}
	expect = "06"
	if !strings.Contains(string(message), expect) {
		log.Fatalf("Could not join channel #r. Expected %q got %q", expect, message)
	}

	_, message, err = conn.ReadMessage()
	if err != nil {
		log.Fatalf("Error while reading connection. Error %s", err.Error())
	}
	expect = "00"
	if !strings.Contains(string(message), expect) {
		log.Fatalf("Could not join channel #r. Expected %q got %q", expect, message)
	}
}

func disconnectUser(conn *gorilla.Conn, chatServer *chat.Server) {
	if err := conn.WriteMessage(1, []byte(`/quit`)); err != nil {
		log.Fatalf("Error writing to connection. Error %s", err)
	}

	if _, _, err := conn.ReadMessage(); err != nil {
		log.Fatalf("Failed reading from WebSocket connection. Error %s", err)
	}

	if chatServer.IsUserConnected("User1") {
		log.Fatal("User is still connected to server after quiting")
	}
}

func connectAndDisconnect(t *testing.T, nickname string, wsPath string, config config.Config, chatServer *chat.Server) {
	conn := connectUser(nickname, wsPath, config)
	joinChannel(conn)
	disconnectUser(conn, chatServer)
}
<|endoftext|>


<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"sync"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/kbfs/tlf"
	"golang.org/x/net/context"
	"golang.org/x/time/rate"
)

// When provisioning a new device from an existing device, the provisionee
// needs one of the existing devices to rekey for it, or it has to use paperkey
// for the rekey. For the case where an existing device does the rekey, there
// are three routines which eventually all go through this rekey queue. These
// three rekey routines are:
//
// 1. When a new device is added, the service on provisioner calls an RPC into
// KBFS, notifying the latter about the new device (provisionee) and that it
// needs rekey.
// 2. On KBFS client, a background routine runs once per hour. It asks the
// mdserver to check for TLFs that needs rekey. Note that this happens on all
// KBFS devices, no matter it has rekey capability or now.
//
// Both 1 and 2 do this by calling MDServerRemote.CheckForRekeys to send back a
// FoldersNeedRekey request.
//
// 3. When the provisionee gets provisioned, it goes through all TLFs and sends
// a MD update for each one of them, by merely copying (since it doesn't have
// access to the key yet) the existing MD revision while setting the rekey bit
// in the flag.

const (
	numConcurrentRekeys            = 64
	rekeysPerSecond     rate.Limit = 16
	rekeyQueueSize                 = 1024 // 24 KB
)

// RekeyQueueStandard implements the RekeyQueue interface.
type RekeyQueueStandard struct {
	config  Config
	log     logger.Logger
	queue   chan tlf.ID
	limiter *rate.Limiter
	cancel  context.CancelFunc

	mu       sync.RWMutex // guards everything below
	pendings map[tlf.ID]bool
}

// Test that RekeyQueueStandard fully implements the RekeyQueue interface.
var _ RekeyQueue = (*RekeyQueueStandard)(nil)

// NewRekeyQueueStandard creates a new rekey queue.
func NewRekeyQueueStandard(config Config) (rkq *RekeyQueueStandard) {
	ctx, cancel := context.WithCancel(context.Background())
	rkq = &RekeyQueueStandard{
		config:   config,
		log:      config.MakeLogger("RQ"),
		queue:    make(chan tlf.ID, rekeyQueueSize),
		limiter:  rate.NewLimiter(rekeysPerSecond, numConcurrentRekeys),
		pendings: make(map[tlf.ID]bool),
		cancel:   cancel,
	}
	rkq.start(ctx)
	return rkq
}

// start spawns a goroutine that dispatches rekey requests to correct folder
// branch ops while conforming to the rater limiter.
func (rkq *RekeyQueueStandard) start(ctx context.Context) {
	go func() {
		for id := range rkq.queue {
			if err := rkq.limiter.Wait(ctx); err != nil {
				rkq.log.Debug("Waiting on rate limiter for tlf=%v error: %v", id, err)
				return
			}
			rkq.config.KBFSOps().RequestRekey(context.Background(), id)
			func(id tlf.ID) {
				rkq.mu.Lock()
				defer rkq.mu.Unlock()
				delete(rkq.pendings, id)
			}(id)
		}
	}()
}

// Enqueue implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) Enqueue(id tlf.ID) {
	rkq.mu.Lock()
	defer rkq.mu.Unlock()
	rkq.pendings[id] = true

	select {
	case rkq.queue <- id:
	default:
		// The queue is full; avoid blocking by spawning a goroutine.
		rkq.log.Debug("Rekey queue is full; enqueuing %s in the background", id)
		go func() { rkq.queue <- id }()
	}
}

// IsRekeyPending implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) IsRekeyPending(id tlf.ID) bool {
	rkq.mu.RLock()
	defer rkq.mu.RUnlock()
	return rkq.pendings[id]
}

// Shutdown implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) Shutdown() {
	rkq.mu.Lock()
	defer rkq.mu.Unlock()
	rkq.cancel()
	rkq.cancel = nil
}
<commit_msg>fix routine leak in rekey_queue (#881)<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"sync"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/kbfs/tlf"
	"golang.org/x/net/context"
	"golang.org/x/time/rate"
)

// When provisioning a new device from an existing device, the provisionee
// needs one of the existing devices to rekey for it, or it has to use paperkey
// for the rekey. For the case where an existing device does the rekey, there
// are three routines which eventually all go through this rekey queue. These
// three rekey routines are:
//
// 1. When a new device is added, the service on provisioner calls an RPC into
// KBFS, notifying the latter about the new device (provisionee) and that it
// needs rekey.
// 2. On KBFS client, a background routine runs once per hour. It asks the
// mdserver to check for TLFs that needs rekey. Note that this happens on all
// KBFS devices, no matter it has rekey capability or now.
//
// Both 1 and 2 do this by calling MDServerRemote.CheckForRekeys to send back a
// FoldersNeedRekey request.
//
// 3. When the provisionee gets provisioned, it goes through all TLFs and sends
// a MD update for each one of them, by merely copying (since it doesn't have
// access to the key yet) the existing MD revision while setting the rekey bit
// in the flag.

const (
	numConcurrentRekeys            = 64
	rekeysPerSecond     rate.Limit = 16
	rekeyQueueSize                 = 1024 // 24 KB
)

// RekeyQueueStandard implements the RekeyQueue interface.
type RekeyQueueStandard struct {
	config  Config
	log     logger.Logger
	queue   chan tlf.ID
	limiter *rate.Limiter
	cancel  context.CancelFunc

	mu       sync.RWMutex // guards everything below
	pendings map[tlf.ID]bool
}

// Test that RekeyQueueStandard fully implements the RekeyQueue interface.
var _ RekeyQueue = (*RekeyQueueStandard)(nil)

// NewRekeyQueueStandard creates a new rekey queue.
func NewRekeyQueueStandard(config Config) (rkq *RekeyQueueStandard) {
	ctx, cancel := context.WithCancel(context.Background())
	rkq = &RekeyQueueStandard{
		config:   config,
		log:      config.MakeLogger("RQ"),
		queue:    make(chan tlf.ID, rekeyQueueSize),
		limiter:  rate.NewLimiter(rekeysPerSecond, numConcurrentRekeys),
		pendings: make(map[tlf.ID]bool),
		cancel:   cancel,
	}
	rkq.start(ctx)
	return rkq
}

// start spawns a goroutine that dispatches rekey requests to correct folder
// branch ops while conforming to the rater limiter.
func (rkq *RekeyQueueStandard) start(ctx context.Context) {
	go func() {
		for {
			select {
			case id := <-rkq.queue:
				if err := rkq.limiter.Wait(ctx); err != nil {
					rkq.log.Debug("Waiting on rate limiter for tlf=%v error: %v", id, err)
					return
				}
				rkq.config.KBFSOps().RequestRekey(context.Background(), id)
				func(id tlf.ID) {
					rkq.mu.Lock()
					defer rkq.mu.Unlock()
					delete(rkq.pendings, id)
				}(id)
			case err := <-ctx.Done():
				rkq.log.Debug("Rekey queue background routine context done: %v", err)
				return
			}
		}
	}()
}

// Enqueue implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) Enqueue(id tlf.ID) {
	rkq.mu.Lock()
	defer rkq.mu.Unlock()
	rkq.pendings[id] = true

	select {
	case rkq.queue <- id:
	default:
		// The queue is full; avoid blocking by spawning a goroutine.
		rkq.log.Debug("Rekey queue is full; enqueuing %s in the background", id)
		go func() { rkq.queue <- id }()
	}
}

// IsRekeyPending implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) IsRekeyPending(id tlf.ID) bool {
	rkq.mu.RLock()
	defer rkq.mu.RUnlock()
	return rkq.pendings[id]
}

// Shutdown implements the RekeyQueue interface for RekeyQueueStandard.
func (rkq *RekeyQueueStandard) Shutdown() {
	rkq.mu.Lock()
	defer rkq.mu.Unlock()
	rkq.cancel()
	rkq.cancel = nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Matthew Collins
//
// 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 ui

import (
	"github.com/thinkofdeath/steven/chat"
	"github.com/thinkofdeath/steven/render"
	"github.com/thinkofdeath/steven/resource/locale"
)

// Formatted is a drawable that draws a string.
type Formatted struct {
	baseElement
	x, y           float64
	MaxWidth       float64
	scaleX, scaleY float64

	Width, Height float64
	Lines         int

	Text []*Text
}

// NewFormatted creates a new Formatted drawable.
func NewFormatted(val chat.AnyComponent, x, y float64) *Formatted {
	f := &Formatted{
		x: x, y: y,
		scaleX: 1, scaleY: 1,
		MaxWidth: -1,
		baseElement: baseElement{
			visible: true,
			isNew:   true,
		},
	}
	f.Update(val)
	return f
}

// NewFormattedWidth creates a new Formatted drawable with a max width.
func NewFormattedWidth(val chat.AnyComponent, x, y, width float64) *Formatted {
	f := &Formatted{
		x: x, y: y,
		scaleX: 1, scaleY: 1,
		MaxWidth: width,
		baseElement: baseElement{
			visible: true,
			isNew:   true,
		},
	}
	f.Update(val)
	return f
}

// Attach changes the location where this is attached to.
func (f *Formatted) Attach(vAttach, hAttach AttachPoint) *Formatted {
	f.vAttach, f.hAttach = vAttach, hAttach
	return f
}

func (f *Formatted) X() float64 { return f.x }
func (f *Formatted) SetX(x float64) {
	if f.x != x {
		f.x = x
		f.dirty = true
	}
}
func (f *Formatted) Y() float64 { return f.y }
func (f *Formatted) SetY(y float64) {
	if f.y != y {
		f.y = y
		f.dirty = true
	}
}
func (f *Formatted) ScaleX() float64 { return f.scaleX }
func (f *Formatted) SetScaleX(s float64) {
	if f.scaleX != s {
		f.scaleX = s
		f.dirty = true
	}
}
func (f *Formatted) ScaleY() float64 { return f.scaleY }
func (f *Formatted) SetScaleY(s float64) {
	if f.scaleY != s {
		f.scaleY = s
		f.dirty = true
	}
}

// Draw draws this to the target region.
func (f *Formatted) Draw(r Region, delta float64) {
	if f.isNew || f.isDirty() || forceDirty {
		cw, ch := f.Size()
		sx, sy := r.W/cw, r.H/ch
		f.data = f.data[:0]
		for _, t := range f.Text {
			r := getDrawRegion(t, sx, sy)
			t.Draw(r, delta)
			f.data = append(f.data, t.data...)
		}
		f.isNew = false
	}
	render.UIAddBytes(f.data)
}

// Offset returns the offset of this drawable from the attachment
// point.
func (f *Formatted) Offset() (float64, float64) {
	return f.x, f.y
}

// Size returns the size of this drawable.
func (f *Formatted) Size() (float64, float64) {
	return (f.Width + 2) * f.scaleX, f.Height * f.scaleY
}

// Remove removes the Formatted element from the draw list.
func (f *Formatted) Remove() {
	Remove(f)
}

// Update updates the component drawn by this drawable.
func (f *Formatted) Update(val chat.AnyComponent) {
	f.Text = f.Text[:0]
	state := formatState{
		f: f,
	}
	state.build(val, func() chat.Color { return chat.White })
	f.Height = float64(state.lines+1) * 18
	f.Width = state.width
	f.Lines = state.lines + 1
	f.dirty = true
}

func (f *Formatted) clearDirty() {
	f.dirty = false
	for _, t := range f.Text {
		t.clearDirty()
	}
}

type formatState struct {
	f      *Formatted
	lines  int
	offset float64
	width  float64
}

func (f *formatState) build(c chat.AnyComponent, color getColorFunc) {
	switch c := c.Value.(type) {
	case *chat.TextComponent:
		gc := getColor(&c.Component, color)
		f.appendText(c.Text, gc)
		for _, e := range c.Extra {
			f.build(e, gc)
		}
	case *chat.TranslateComponent:
		gc := getColor(&c.Component, color)
		for _, part := range locale.Get(c.Translate) {
			switch part := part.(type) {
			case string:
				f.appendText(part, gc)
			case int:
				if part < 0 || part >= len(c.With) {
					continue
				}
				f.build(c.With[part], gc)
			}
		}

	default:
		panic("unhandled component")
	}
}

func (f *formatState) appendText(text string, color getColorFunc) {
	width := 0.0
	last := 0
	for i, r := range text {
		s := render.SizeOfCharacter(r) + 2
		if (f.f.MaxWidth > 0 && f.offset+width+s > f.f.MaxWidth) || r == '\n' {
			rr, gg, bb := colorRGB(color())
			txt := NewText(text[last:i], f.offset, float64(f.lines*18+1), rr, gg, bb)
			txt.AttachTo(f.f)
			last = i
			if r == '\n' {
				last++
			}
			f.f.Text = append(f.f.Text, txt)
			f.offset = 0
			f.lines++
			width = 0
		}
		width += s
		if f.offset+width > f.width {
			f.width = f.offset + width
		}
	}
	if last != len(text) {
		r, g, b := colorRGB(color())
		txt := NewText(text[last:], f.offset, float64(f.lines*18+1), r, g, b)
		txt.AttachTo(f.f)
		f.f.Text = append(f.f.Text, txt)
		f.offset += txt.Width + 2
		if f.offset > f.width {
			f.width = f.offset
		}
	}
}

type getColorFunc func() chat.Color

func getColor(c *chat.Component, parent getColorFunc) getColorFunc {
	return func() chat.Color {
		if c.Color != "" {
			return c.Color
		}
		if parent != nil {
			return parent()
		}
		return chat.White
	}
}

func colorRGB(c chat.Color) (r, g, b int) {
	switch c {
	case chat.Black:
		return 0, 0, 0
	case chat.DarkBlue:
		return 0, 0, 170
	case chat.DarkGreen:
		return 0, 170, 0
	case chat.DarkAqua:
		return 0, 170, 170
	case chat.DarkRed:
		return 170, 0, 0
	case chat.DarkPurple:
		return 170, 0, 170
	case chat.Gold:
		return 255, 170, 0
	case chat.Gray:
		return 170, 170, 170
	case chat.DarkGray:
		return 85, 85, 85
	case chat.Blue:
		return 85, 85, 255
	case chat.Green:
		return 85, 255, 85
	case chat.Aqua:
		return 85, 255, 255
	case chat.Red:
		return 255, 85, 85
	case chat.LightPurple:
		return 255, 85, 255
	case chat.Yellow:
		return 255, 255, 85
	case chat.White:
		return 255, 255, 255
	}
	return 255, 255, 255
}
<commit_msg>ui: correctly flag Formatted as dirty when sub elements are dirty<commit_after>// Copyright 2015 Matthew Collins
//
// 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 ui

import (
	"github.com/thinkofdeath/steven/chat"
	"github.com/thinkofdeath/steven/render"
	"github.com/thinkofdeath/steven/resource/locale"
)

// Formatted is a drawable that draws a string.
type Formatted struct {
	baseElement
	x, y           float64
	MaxWidth       float64
	scaleX, scaleY float64

	Width, Height float64
	Lines         int

	Text []*Text
}

// NewFormatted creates a new Formatted drawable.
func NewFormatted(val chat.AnyComponent, x, y float64) *Formatted {
	f := &Formatted{
		x: x, y: y,
		scaleX: 1, scaleY: 1,
		MaxWidth: -1,
		baseElement: baseElement{
			visible: true,
			isNew:   true,
		},
	}
	f.Update(val)
	return f
}

// NewFormattedWidth creates a new Formatted drawable with a max width.
func NewFormattedWidth(val chat.AnyComponent, x, y, width float64) *Formatted {
	f := &Formatted{
		x: x, y: y,
		scaleX: 1, scaleY: 1,
		MaxWidth: width,
		baseElement: baseElement{
			visible: true,
			isNew:   true,
		},
	}
	f.Update(val)
	return f
}

// Attach changes the location where this is attached to.
func (f *Formatted) Attach(vAttach, hAttach AttachPoint) *Formatted {
	f.vAttach, f.hAttach = vAttach, hAttach
	return f
}

func (f *Formatted) X() float64 { return f.x }
func (f *Formatted) SetX(x float64) {
	if f.x != x {
		f.x = x
		f.dirty = true
	}
}
func (f *Formatted) Y() float64 { return f.y }
func (f *Formatted) SetY(y float64) {
	if f.y != y {
		f.y = y
		f.dirty = true
	}
}
func (f *Formatted) ScaleX() float64 { return f.scaleX }
func (f *Formatted) SetScaleX(s float64) {
	if f.scaleX != s {
		f.scaleX = s
		f.dirty = true
	}
}
func (f *Formatted) ScaleY() float64 { return f.scaleY }
func (f *Formatted) SetScaleY(s float64) {
	if f.scaleY != s {
		f.scaleY = s
		f.dirty = true
	}
}

// Draw draws this to the target region.
func (f *Formatted) Draw(r Region, delta float64) {
	if f.isNew || f.isDirty() || forceDirty {
		cw, ch := f.Size()
		sx, sy := r.W/cw, r.H/ch
		f.data = f.data[:0]
		for _, t := range f.Text {
			r := getDrawRegion(t, sx, sy)
			t.Draw(r, delta)
			f.data = append(f.data, t.data...)
		}
		f.isNew = false
	}
	render.UIAddBytes(f.data)
}

// Offset returns the offset of this drawable from the attachment
// point.
func (f *Formatted) Offset() (float64, float64) {
	return f.x, f.y
}

// Size returns the size of this drawable.
func (f *Formatted) Size() (float64, float64) {
	return (f.Width + 2) * f.scaleX, f.Height * f.scaleY
}

// Remove removes the Formatted element from the draw list.
func (f *Formatted) Remove() {
	Remove(f)
}

// Update updates the component drawn by this drawable.
func (f *Formatted) Update(val chat.AnyComponent) {
	f.Text = f.Text[:0]
	state := formatState{
		f: f,
	}
	state.build(val, func() chat.Color { return chat.White })
	f.Height = float64(state.lines+1) * 18
	f.Width = state.width
	f.Lines = state.lines + 1
	f.dirty = true
}
func (f *Formatted) isDirty() bool {
	if f.baseElement.isDirty() {
		return true
	}
	for _, t := range f.Text {
		if t.dirty {
			return true
		}
	}
	return false
}

func (f *Formatted) clearDirty() {
	f.dirty = false
	for _, t := range f.Text {
		t.clearDirty()
	}
}

type formatState struct {
	f      *Formatted
	lines  int
	offset float64
	width  float64
}

func (f *formatState) build(c chat.AnyComponent, color getColorFunc) {
	switch c := c.Value.(type) {
	case *chat.TextComponent:
		gc := getColor(&c.Component, color)
		f.appendText(c.Text, gc)
		for _, e := range c.Extra {
			f.build(e, gc)
		}
	case *chat.TranslateComponent:
		gc := getColor(&c.Component, color)
		for _, part := range locale.Get(c.Translate) {
			switch part := part.(type) {
			case string:
				f.appendText(part, gc)
			case int:
				if part < 0 || part >= len(c.With) {
					continue
				}
				f.build(c.With[part], gc)
			}
		}

	default:
		panic("unhandled component")
	}
}

func (f *formatState) appendText(text string, color getColorFunc) {
	width := 0.0
	last := 0
	for i, r := range text {
		s := render.SizeOfCharacter(r) + 2
		if (f.f.MaxWidth > 0 && f.offset+width+s > f.f.MaxWidth) || r == '\n' {
			rr, gg, bb := colorRGB(color())
			txt := NewText(text[last:i], f.offset, float64(f.lines*18+1), rr, gg, bb)
			txt.AttachTo(f.f)
			last = i
			if r == '\n' {
				last++
			}
			f.f.Text = append(f.f.Text, txt)
			f.offset = 0
			f.lines++
			width = 0
		}
		width += s
		if f.offset+width > f.width {
			f.width = f.offset + width
		}
	}
	if last != len(text) {
		r, g, b := colorRGB(color())
		txt := NewText(text[last:], f.offset, float64(f.lines*18+1), r, g, b)
		txt.AttachTo(f.f)
		f.f.Text = append(f.f.Text, txt)
		f.offset += txt.Width + 2
		if f.offset > f.width {
			f.width = f.offset
		}
	}
}

type getColorFunc func() chat.Color

func getColor(c *chat.Component, parent getColorFunc) getColorFunc {
	return func() chat.Color {
		if c.Color != "" {
			return c.Color
		}
		if parent != nil {
			return parent()
		}
		return chat.White
	}
}

func colorRGB(c chat.Color) (r, g, b int) {
	switch c {
	case chat.Black:
		return 0, 0, 0
	case chat.DarkBlue:
		return 0, 0, 170
	case chat.DarkGreen:
		return 0, 170, 0
	case chat.DarkAqua:
		return 0, 170, 170
	case chat.DarkRed:
		return 170, 0, 0
	case chat.DarkPurple:
		return 170, 0, 170
	case chat.Gold:
		return 255, 170, 0
	case chat.Gray:
		return 170, 170, 170
	case chat.DarkGray:
		return 85, 85, 85
	case chat.Blue:
		return 85, 85, 255
	case chat.Green:
		return 85, 255, 85
	case chat.Aqua:
		return 85, 255, 255
	case chat.Red:
		return 255, 85, 85
	case chat.LightPurple:
		return 255, 85, 255
	case chat.Yellow:
		return 255, 255, 85
	case chat.White:
		return 255, 255, 255
	}
	return 255, 255, 255
}
<|endoftext|>


<commit_before>package chunkstore

import (
    "compress/gzip"
    "compress/zlib"
    "encoding/binary"
    "fmt"
    "io"
    "os"
    "path"

    .   "chunkymonkey/types"
)

const (
    regionFileEdge       = 32
    regionFileEdgeShift  = 5
    regionFileSectorSize = 4096
)

type chunkStoreBeta struct {
    worldPath   string
    regionFiles map[uint64]*regionFileReader
}

// Creates a ChunkStore that reads the Minecraft Beta world format.
func NewChunkStoreBeta(worldPath string) ChunkStore {
    return &chunkStoreBeta{
        worldPath:   worldPath,
        regionFiles: make(map[uint64]*regionFileReader),
    }
}

func (s *chunkStoreBeta) LoadChunk(chunkLoc *ChunkXz) (reader ChunkReader, err os.Error) {
    regionLoc := regionLocForChunkXz(chunkLoc)

    var cfr *regionFileReader
    cfr, ok := s.regionFiles[regionLoc.regionKey()]
    if !ok {
        // TODO limit number of regionFileReader objs to a maximum number of
        // most-frequently-used regions. Close regionFileReader objects when no
        // longer needed.
        filePath := regionLoc.regionFilePath(s.worldPath)
        cfr, err = newRegionFileReader(filePath)
        if err != nil {
            return
        }
    }

    return cfr.ReadChunkData(chunkLoc)
}

// A chunk file header entry.
type chunkOffset uint32

// Returns true if the offset value states that the chunk is present in the
// file.
func (o chunkOffset) IsPresent() bool {
    return o != 0
}

func (o chunkOffset) Get() (sectorCount, sectorIndex uint32) {
    sectorCount = uint32(o & 0xff)
    sectorIndex = uint32(o >> 8)
    return
}

// Represents a chunk file header containing chunk data offsets.
type regionFileHeader [regionFileEdge * regionFileEdge]chunkOffset

// Returns the chunk offset data for the given chunk. It assumes that chunkLoc
// is within the chunk file - discarding upper bits of the X and Z coords.
func (h regionFileHeader) GetOffset(chunkLoc *ChunkXz) chunkOffset {
    x := chunkLoc.X & (regionFileEdge - 1)
    z := chunkLoc.Z & (regionFileEdge - 1)
    return h[x+(z<<regionFileEdgeShift)]
}

// Represents the header of a single chunk of data within a chunkfile.
type chunkDataHeader struct {
    DataSize uint32
    Version  byte
}

// Returns an io.Reader to correctly decompress data from the chunk data.
// The reader passed in must be just after the chunkDataHeader in the source
// data stream. The caller is responsible for closing the returned ReadCloser.
func (cdh *chunkDataHeader) GetDataReader(raw io.Reader) (output io.ReadCloser, err os.Error) {
    limitReader := io.LimitReader(raw, int64(cdh.DataSize))
    switch cdh.Version {
    case 1:
        output, err = gzip.NewReader(limitReader)
    case 2:
        output, err = zlib.NewReader(limitReader)
    default:
        err = os.NewError("Chunk data header contained unknown version number.")
    }
    return
}

// Handle on a chunk file - used to read chunk data from the file.
type regionFileReader struct {
    offsets  regionFileHeader
    file     *os.File
}

func newRegionFileReader(filePath string) (cfr *regionFileReader, err os.Error) {
    file, err := os.Open(filePath, os.O_RDONLY, 0)
    if err != nil {
        return
    }

    cfr = &regionFileReader{
        file: file,
    }

    err = binary.Read(file, binary.BigEndian, &cfr.offsets)
    if err != nil {
        cfr = nil
        return
    }

    return
}

func (cfr *regionFileReader) Close() {
    cfr.file.Close()
}

func (cfr *regionFileReader) ReadChunkData(chunkLoc *ChunkXz) (r *chunkReader, err os.Error) {
    offset := cfr.offsets.GetOffset(chunkLoc)

    if !offset.IsPresent() {
        // Chunk doesn't exist in file
        err = nil
        return
    }

    sectorCount, sectorIndex := offset.Get()

    if sectorIndex == 0 || sectorCount == 0 {
        err = os.NewError("Header gave bad chunk offset.")
    }

    cfr.file.Seek(int64(sectorIndex)*regionFileSectorSize, 0)

    // 5 is the size of chunkDataHeader in bytes.
    maxChunkDataSize := (sectorCount * regionFileSectorSize) - 5

    var header chunkDataHeader
    binary.Read(cfr.file, binary.BigEndian, &header)
    if header.DataSize > maxChunkDataSize {
        err = os.NewError("Chunk is too big for the sectors it is within.")
    }

    dataReader, err := header.GetDataReader(cfr.file)
    if err != nil {
        return
    }

    defer dataReader.Close()
    r, err = newChunkReader(dataReader)

    return
}

type regionCoord int32

type regionLoc struct {
    X, Z regionCoord
}

func regionLocForChunkXz(chunkLoc *ChunkXz) regionLoc {
    return regionLoc {
        regionCoord(chunkLoc.X>>regionFileEdgeShift),
        regionCoord(chunkLoc.Z>>regionFileEdgeShift),
    }
}

func (loc *regionLoc) regionKey() uint64 {
    return uint64(loc.X) << 32 | uint64(uint32(loc.Z))
}

func (loc *regionLoc) regionFilePath(worldPath string) string {
    return path.Join(
        worldPath,
        "region",
        fmt.Sprintf("r.%d.%d.mcr", loc.X, loc.Z),
    )
}
<commit_msg>Some minor beta chunk store fixes. Including actually *caching* region files.<commit_after>package chunkstore

import (
    "compress/gzip"
    "compress/zlib"
    "encoding/binary"
    "fmt"
    "io"
    "os"
    "path"

    .   "chunkymonkey/types"
)

const (
    regionFileEdge       = 32
    regionFileEdgeShift  = 5
    regionFileSectorSize = 4096
)

type chunkStoreBeta struct {
    worldPath   string
    regionFiles map[uint64]*regionFileReader
}

// Creates a ChunkStore that reads the Minecraft Beta world format.
func NewChunkStoreBeta(worldPath string) ChunkStore {
    return &chunkStoreBeta{
        worldPath:   worldPath,
        regionFiles: make(map[uint64]*regionFileReader),
    }
}

func (s *chunkStoreBeta) LoadChunk(chunkLoc *ChunkXz) (reader ChunkReader, err os.Error) {
    regionLoc := regionLocForChunkXz(chunkLoc)

    var cfr *regionFileReader
    cfr, ok := s.regionFiles[regionLoc.regionKey()]
    if !ok {
        // TODO limit number of regionFileReader objs to a maximum number of
        // most-frequently-used regions. Close regionFileReader objects when no
        // longer needed.
        filePath := regionLoc.regionFilePath(s.worldPath)
        cfr, err = newRegionFileReader(filePath)
        if err != nil {
            return
        }
        s.regionFiles[regionLoc.regionKey()] = cfr
    }

    return cfr.ReadChunkData(chunkLoc)
}

// A chunk file header entry.
type chunkOffset uint32

// Returns true if the offset value states that the chunk is present in the
// file.
func (o chunkOffset) IsPresent() bool {
    return o != 0
}

func (o chunkOffset) Get() (sectorCount, sectorIndex uint32) {
    sectorCount = uint32(o & 0xff)
    sectorIndex = uint32(o >> 8)
    return
}

// Represents a chunk file header containing chunk data offsets.
type regionFileHeader [regionFileEdge * regionFileEdge]chunkOffset

// Returns the chunk offset data for the given chunk. It assumes that chunkLoc
// is within the chunk file - discarding upper bits of the X and Z coords.
func (h regionFileHeader) GetOffset(chunkLoc *ChunkXz) chunkOffset {
    x := chunkLoc.X & (regionFileEdge - 1)
    z := chunkLoc.Z & (regionFileEdge - 1)
    return h[x+(z<<regionFileEdgeShift)]
}

// Represents the header of a single chunk of data within a chunkfile.
type chunkDataHeader struct {
    DataSize uint32
    Version  byte
}

// Returns an io.Reader to correctly decompress data from the chunk data.
// The reader passed in must be just after the chunkDataHeader in the source
// data stream. The caller is responsible for closing the returned ReadCloser.
func (cdh *chunkDataHeader) GetDataReader(raw io.Reader) (output io.ReadCloser, err os.Error) {
    limitReader := io.LimitReader(raw, int64(cdh.DataSize))
    switch cdh.Version {
    case 1:
        output, err = gzip.NewReader(limitReader)
    case 2:
        output, err = zlib.NewReader(limitReader)
    default:
        err = os.NewError("Chunk data header contained unknown version number.")
    }
    return
}

// Handle on a chunk file - used to read chunk data from the file.
type regionFileReader struct {
    offsets  regionFileHeader
    file     *os.File
}

func newRegionFileReader(filePath string) (cfr *regionFileReader, err os.Error) {
    file, err := os.Open(filePath, os.O_RDONLY, 0)
    if err != nil {
        return
    }

    cfr = &regionFileReader{
        file: file,
    }

    err = binary.Read(file, binary.BigEndian, &cfr.offsets)
    if err != nil {
        cfr = nil
        return
    }

    return
}

func (cfr *regionFileReader) Close() {
    cfr.file.Close()
}

func (cfr *regionFileReader) ReadChunkData(chunkLoc *ChunkXz) (r *chunkReader, err os.Error) {
    offset := cfr.offsets.GetOffset(chunkLoc)

    if !offset.IsPresent() {
        // Chunk doesn't exist in file
        err = nil
        return
    }

    sectorCount, sectorIndex := offset.Get()

    if sectorIndex == 0 || sectorCount == 0 {
        err = os.NewError("Header gave bad chunk offset.")
        return
    }

    cfr.file.Seek(int64(sectorIndex)*regionFileSectorSize, 0)

    // 5 is the size of chunkDataHeader in bytes.
    maxChunkDataSize := (sectorCount * regionFileSectorSize) - 5

    var header chunkDataHeader
    binary.Read(cfr.file, binary.BigEndian, &header)
    if header.DataSize > maxChunkDataSize {
        err = os.NewError("Chunk is too big for the sectors it is within.")
        return
    }

    dataReader, err := header.GetDataReader(cfr.file)
    if err != nil {
        return
    }
    defer dataReader.Close()

    r, err = newChunkReader(dataReader)

    return
}

type regionCoord int32

type regionLoc struct {
    X, Z regionCoord
}

func regionLocForChunkXz(chunkLoc *ChunkXz) regionLoc {
    return regionLoc {
        regionCoord(chunkLoc.X>>regionFileEdgeShift),
        regionCoord(chunkLoc.Z>>regionFileEdgeShift),
    }
}

func (loc *regionLoc) regionKey() uint64 {
    return uint64(loc.X) << 32 | uint64(uint32(loc.Z))
}

func (loc *regionLoc) regionFilePath(worldPath string) string {
    return path.Join(
        worldPath,
        "region",
        fmt.Sprintf("r.%d.%d.mcr", loc.X, loc.Z),
    )
}
<|endoftext|>


<commit_before>package term

import (
	"io"

	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"

	"github.com/gdamore/tcell"
	"github.com/mibk/syd/ui"
)

type UI struct {
	screen        tcell.Screen
	wasBtnPressed bool

	windows []*Window
}

func (t *UI) Init() error {
	sc, err := tcell.NewScreen()
	if err != nil {
		return err
	}
	if err := sc.Init(); err != nil {
		return err
	}
	sc.EnableMouse()
	t.screen = sc

	go t.translateEvents()
	return nil
}

func (t *UI) Close() error {
	t.screen.Fini()
	return nil
}

func (t *UI) Size() (w, h int) { return t.screen.Size() }

func (t *UI) NewWindow() *Window {
	head := &Text{
		frame: new(Frame),
		bgstyle: tcell.StyleDefault.
			Background(tcell.GetColor("#eaffff")),
		hlstyle: tcell.StyleDefault.
			Background(tcell.GetColor("#90e0e0")),
	}
	body := &Text{
		frame: new(Frame),
		bgstyle: tcell.StyleDefault.
			Background(tcell.GetColor("#ffffea")),
		hlstyle: tcell.StyleDefault.
			Background(tcell.GetColor("#e0e090")),
	}
	win := &Window{
		x: 1, y: 1, // For testing purposes.
		ui:     t,
		head:   head,
		body:   body,
		active: body,
	}
	head.win = win
	body.win = win
	t.windows = append(t.windows, win)
	return win
}

// TODO: This is for temporary reasons. Remove it.
func (t *UI) Push_Mouse_Event(ev mouse.Event) {
	win := t.windows[0] // TODO: It may not exist.
	if int(ev.Y) >= win.body.y {
		win.body.click(ev)
		win.active = win.body
	} else {
		win.head.click(ev)
		win.active = win.head
	}
}

func (t *UI) Push_Key_Event(ev key.Event) {
	t.windows[0].active.keyEventHandler(ev)
}

type Window struct {
	ui *UI

	width, height int
	x, y          int

	head   *Text
	body   *Text
	active *Text // will receive key events
}

func (win *Window) Size() (w, h int) {
	// TODO: Return the width and height of the window.
	w, h = win.ui.Size()
	return w / 2, h
}

func (win *Window) Head() *Text { return win.head }
func (win *Window) Body() *Text { return win.body }

func (win *Window) Clear() {
	win.head.clear()
	win.body.clear()
}

func (win *Window) Flush() {
	_, height := win.Size()
	win.head.x = win.x
	win.head.y = win.y
	win.body.x = win.x
	win.head.flush()
	win.body.y = win.y + len(win.head.frame.lines)
	win.ui.screen.HideCursor()
	win.body.flush()
	win.body.fill(height)
	win.ui.screen.Show()
}

type Text struct {
	win   *Window
	frame *Frame

	x, y int
	cur  struct {
		p0, p1 int // char position
		x, y   int // current position
	}

	// styles
	bgstyle tcell.Style
	hlstyle tcell.Style

	mouseEventHandler ui.MouseEventHandler
	keyEventHandler   ui.KeyEventHandler
}

func (t *Text) click(ev mouse.Event) {
	if t.mouseEventHandler == nil {
		return
	}
	p := t.frame.CharsUntilXY(int(ev.X)-t.x, int(ev.Y)-t.y)
	t.mouseEventHandler(p, ev)
}

func (t *Text) OnMouseEvent(h ui.MouseEventHandler) {
	t.mouseEventHandler = h
}

func (t *Text) OnKeyEvent(h ui.KeyEventHandler) {
	t.keyEventHandler = h
}

func (t *Text) clear() {
	*t.frame = Frame{
		lines:   make([][]rune, 1),
		wantCol: t.frame.wantCol,
	}
	t.cur.x, t.cur.y = 0, 0
	t.checkSelection()
}

func (t *Text) Select(p0, p1 int) { t.cur.p0, t.cur.p1 = p0, p1 }

func (t *Text) WriteRune(r rune) error {
	if r != '\n' {
		t.frame.lines[t.cur.y] = append(t.frame.lines[t.cur.y], r)
	}

	w, h := t.win.Size()
	if t.cur.x >= w || r == '\n' {
		t.cur.y++
		t.cur.x = 0
		t.frame.lines = append(t.frame.lines, nil)
		if t.cur.y == h {
			return io.EOF
		}
	} else if r == '\t' {
		t.cur.x += tabWidthForCol(t.cur.x)
	} else {
		t.cur.x++
	}
	t.frame.nchars++
	t.checkSelection()
	return nil
}

// checkSelection tries to line0, line1, and wantCol.
func (t *Text) checkSelection() {
	if t.cur.p0 == t.frame.nchars {
		t.frame.line0 = t.cur.y
		if t.frame.wantCol == ui.ColQ0 {
			t.frame.wantCol = t.cur.x
		}
	}
	if t.cur.p1 == t.frame.nchars {
		t.frame.line1 = t.cur.y
		if t.frame.wantCol == ui.ColQ1 {
			t.frame.wantCol = t.cur.x
		}
	}
}

var reverse = tcell.StyleDefault.Reverse(true)

func (t *Text) flush() {
	width, _ := t.win.Size()
	style := t.bgstyle
	selText := func(p, x, y int) {
		if p == t.cur.p0 && t.cur.p0 == t.cur.p1 {
			style = reverse
		} else if p >= t.cur.p0 && p < t.cur.p1 {
			style = t.hlstyle
		} else {
			style = t.bgstyle
		}
	}
	p := 0
	for y, l := range t.frame.lines {
		x := 0
		for _, r := range l {
			selText(p, x, y)
			w := 1
			if r == '\t' {
				r = ' '
				w = tabWidthForCol(x)

			}
			for i := 0; i < w; i++ {
				t.win.ui.screen.SetContent(t.x+x, t.y+y, r, nil, style)
				x += 1
				if style == reverse {
					style = t.bgstyle
				}
			}
			p++
		}
		selText(p, x, y)
		for ; x < width; x++ {
			t.win.ui.screen.SetContent(t.x+x, t.y+y, ' ', nil, style)
			if style == reverse {
				style = t.bgstyle
			}
		}
		p++
	}
}

func (t *Text) fill(height int) {
	width, _ := t.win.Size()
	for y := len(t.frame.lines) + t.y; y < height; y++ {
		for x := 0; x < width; x++ {
			t.win.ui.screen.SetContent(t.win.x+x, y, ' ', nil, t.bgstyle)
		}
	}
}

func (t *Text) Frame() *Frame { return t.frame }

type Frame struct {
	lines   [][]rune
	line0   int
	line1   int
	wantCol int
	nchars  int
}

func (f *Frame) Nchars() int                { return f.nchars }
func (f *Frame) SelectionLines() (int, int) { return f.line0, f.line1 }

func (f *Frame) CharsUntilXY(x, y int) int {
	if y >= len(f.lines) {
		return f.nchars
	}
	var p int
	for n, l := range f.lines {
		if n == y {
			return p + charsUntilX(l, x)
		}
		p += len(l) + 1 // + '\n'
	}
	return 0
}

func charsUntilX(s []rune, x int) int {
	var w int
	for i, r := range s {
		if r == '\t' {
			w += tabWidthForCol(w)
		} else {
			w += 1
		}
		if w > x {
			return i
		}
	}
	return len(s)
}

const tabStop = 8

func tabWidthForCol(col int) int {
	w := tabStop - col%tabStop
	if w == 0 {
		return tabStop
	}
	return w
}

func (f *Frame) MaxLines() int { panic("not implemented") }
func (f *Frame) Lines() int    { return len(f.lines) }

func (f *Frame) WantCol() int       { return f.wantCol }
func (f *Frame) SetWantCol(col int) { f.wantCol = col }
<commit_msg>ui/term: Remove unused Frame.MaxLines<commit_after>package term

import (
	"io"

	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"

	"github.com/gdamore/tcell"
	"github.com/mibk/syd/ui"
)

type UI struct {
	screen        tcell.Screen
	wasBtnPressed bool

	windows []*Window
}

func (t *UI) Init() error {
	sc, err := tcell.NewScreen()
	if err != nil {
		return err
	}
	if err := sc.Init(); err != nil {
		return err
	}
	sc.EnableMouse()
	t.screen = sc

	go t.translateEvents()
	return nil
}

func (t *UI) Close() error {
	t.screen.Fini()
	return nil
}

func (t *UI) Size() (w, h int) { return t.screen.Size() }

func (t *UI) NewWindow() *Window {
	head := &Text{
		frame: new(Frame),
		bgstyle: tcell.StyleDefault.
			Background(tcell.GetColor("#eaffff")),
		hlstyle: tcell.StyleDefault.
			Background(tcell.GetColor("#90e0e0")),
	}
	body := &Text{
		frame: new(Frame),
		bgstyle: tcell.StyleDefault.
			Background(tcell.GetColor("#ffffea")),
		hlstyle: tcell.StyleDefault.
			Background(tcell.GetColor("#e0e090")),
	}
	win := &Window{
		x: 1, y: 1, // For testing purposes.
		ui:     t,
		head:   head,
		body:   body,
		active: body,
	}
	head.win = win
	body.win = win
	t.windows = append(t.windows, win)
	return win
}

// TODO: This is for temporary reasons. Remove it.
func (t *UI) Push_Mouse_Event(ev mouse.Event) {
	win := t.windows[0] // TODO: It may not exist.
	if int(ev.Y) >= win.body.y {
		win.body.click(ev)
		win.active = win.body
	} else {
		win.head.click(ev)
		win.active = win.head
	}
}

func (t *UI) Push_Key_Event(ev key.Event) {
	t.windows[0].active.keyEventHandler(ev)
}

type Window struct {
	ui *UI

	width, height int
	x, y          int

	head   *Text
	body   *Text
	active *Text // will receive key events
}

func (win *Window) Size() (w, h int) {
	// TODO: Return the width and height of the window.
	w, h = win.ui.Size()
	return w / 2, h
}

func (win *Window) Head() *Text { return win.head }
func (win *Window) Body() *Text { return win.body }

func (win *Window) Clear() {
	win.head.clear()
	win.body.clear()
}

func (win *Window) Flush() {
	_, height := win.Size()
	win.head.x = win.x
	win.head.y = win.y
	win.body.x = win.x
	win.head.flush()
	win.body.y = win.y + len(win.head.frame.lines)
	win.ui.screen.HideCursor()
	win.body.flush()
	win.body.fill(height)
	win.ui.screen.Show()
}

type Text struct {
	win   *Window
	frame *Frame

	x, y int
	cur  struct {
		p0, p1 int // char position
		x, y   int // current position
	}

	// styles
	bgstyle tcell.Style
	hlstyle tcell.Style

	mouseEventHandler ui.MouseEventHandler
	keyEventHandler   ui.KeyEventHandler
}

func (t *Text) click(ev mouse.Event) {
	if t.mouseEventHandler == nil {
		return
	}
	p := t.frame.CharsUntilXY(int(ev.X)-t.x, int(ev.Y)-t.y)
	t.mouseEventHandler(p, ev)
}

func (t *Text) OnMouseEvent(h ui.MouseEventHandler) {
	t.mouseEventHandler = h
}

func (t *Text) OnKeyEvent(h ui.KeyEventHandler) {
	t.keyEventHandler = h
}

func (t *Text) clear() {
	*t.frame = Frame{
		lines:   make([][]rune, 1),
		wantCol: t.frame.wantCol,
	}
	t.cur.x, t.cur.y = 0, 0
	t.checkSelection()
}

func (t *Text) Select(p0, p1 int) { t.cur.p0, t.cur.p1 = p0, p1 }

func (t *Text) WriteRune(r rune) error {
	if r != '\n' {
		t.frame.lines[t.cur.y] = append(t.frame.lines[t.cur.y], r)
	}

	w, h := t.win.Size()
	if t.cur.x >= w || r == '\n' {
		t.cur.y++
		t.cur.x = 0
		t.frame.lines = append(t.frame.lines, nil)
		if t.cur.y == h {
			return io.EOF
		}
	} else if r == '\t' {
		t.cur.x += tabWidthForCol(t.cur.x)
	} else {
		t.cur.x++
	}
	t.frame.nchars++
	t.checkSelection()
	return nil
}

// checkSelection tries to line0, line1, and wantCol.
func (t *Text) checkSelection() {
	if t.cur.p0 == t.frame.nchars {
		t.frame.line0 = t.cur.y
		if t.frame.wantCol == ui.ColQ0 {
			t.frame.wantCol = t.cur.x
		}
	}
	if t.cur.p1 == t.frame.nchars {
		t.frame.line1 = t.cur.y
		if t.frame.wantCol == ui.ColQ1 {
			t.frame.wantCol = t.cur.x
		}
	}
}

var reverse = tcell.StyleDefault.Reverse(true)

func (t *Text) flush() {
	width, _ := t.win.Size()
	style := t.bgstyle
	selText := func(p, x, y int) {
		if p == t.cur.p0 && t.cur.p0 == t.cur.p1 {
			style = reverse
		} else if p >= t.cur.p0 && p < t.cur.p1 {
			style = t.hlstyle
		} else {
			style = t.bgstyle
		}
	}
	p := 0
	for y, l := range t.frame.lines {
		x := 0
		for _, r := range l {
			selText(p, x, y)
			w := 1
			if r == '\t' {
				r = ' '
				w = tabWidthForCol(x)

			}
			for i := 0; i < w; i++ {
				t.win.ui.screen.SetContent(t.x+x, t.y+y, r, nil, style)
				x += 1
				if style == reverse {
					style = t.bgstyle
				}
			}
			p++
		}
		selText(p, x, y)
		for ; x < width; x++ {
			t.win.ui.screen.SetContent(t.x+x, t.y+y, ' ', nil, style)
			if style == reverse {
				style = t.bgstyle
			}
		}
		p++
	}
}

func (t *Text) fill(height int) {
	width, _ := t.win.Size()
	for y := len(t.frame.lines) + t.y; y < height; y++ {
		for x := 0; x < width; x++ {
			t.win.ui.screen.SetContent(t.win.x+x, y, ' ', nil, t.bgstyle)
		}
	}
}

func (t *Text) Frame() *Frame { return t.frame }

type Frame struct {
	lines   [][]rune
	line0   int
	line1   int
	wantCol int
	nchars  int
}

func (f *Frame) Nchars() int                { return f.nchars }
func (f *Frame) SelectionLines() (int, int) { return f.line0, f.line1 }

func (f *Frame) CharsUntilXY(x, y int) int {
	if y >= len(f.lines) {
		return f.nchars
	}
	var p int
	for n, l := range f.lines {
		if n == y {
			return p + charsUntilX(l, x)
		}
		p += len(l) + 1 // + '\n'
	}
	return 0
}

func charsUntilX(s []rune, x int) int {
	var w int
	for i, r := range s {
		if r == '\t' {
			w += tabWidthForCol(w)
		} else {
			w += 1
		}
		if w > x {
			return i
		}
	}
	return len(s)
}

const tabStop = 8

func tabWidthForCol(col int) int {
	w := tabStop - col%tabStop
	if w == 0 {
		return tabStop
	}
	return w
}

func (f *Frame) Lines() int         { return len(f.lines) }
func (f *Frame) WantCol() int       { return f.wantCol }
func (f *Frame) SetWantCol(col int) { f.wantCol = col }
<|endoftext|>