<commit_before>/*
Copyright 2021 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sharded

import (
	"context"
	"flag"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"

	"vitess.io/vitess/go/test/utils"

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

	"vitess.io/vitess/go/mysql"
	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/test/endtoend/cluster"
)

var (
	clusterInstance *cluster.LocalProcessCluster
	vtParams        mysql.ConnParams
	KeyspaceName    = "ks"
	Cell            = "test"
	SchemaSQL       = `
create table t2(
	id3 bigint,
	id4 bigint,
	primary key(id3)
) Engine=InnoDB;

create table t2_id4_idx(
	id bigint not null auto_increment,
	id4 bigint,
	id3 bigint,
	primary key(id),
	key idx_id4(id4)
) Engine=InnoDB;

create table t8(
	id8 bigint,
	testId bigint,
	primary key(id8)
) Engine=InnoDB;

create table t9(
	id9 bigint,
	testId bigint,
	primary key(id9)
) Engine=InnoDB;
`

	VSchema = `
{
  "sharded": true,
  "vindexes": {
    "unicode_loose_xxhash" : {
	  "type": "unicode_loose_xxhash"
    },
    "unicode_loose_md5" : {
	  "type": "unicode_loose_md5"
    },
    "hash": {
      "type": "hash"
    },
    "xxhash": {
      "type": "xxhash"
    },
    "t2_id4_idx": {
      "type": "lookup_hash",
      "params": {
        "table": "t2_id4_idx",
        "from": "id4",
        "to": "id3",
        "autocommit": "true"
      },
      "owner": "t2"
    }
  },
  "tables": {
    "t2": {
      "column_vindexes": [
        {
          "column": "id3",
          "name": "hash"
        },
        {
          "column": "id4",
          "name": "t2_id4_idx"
        }
      ]
    },
    "t2_id4_idx": {
      "column_vindexes": [
        {
          "column": "id4",
          "name": "hash"
        }
      ]
    },
    "t8": {
      "column_vindexes": [
        {
          "column": "id8",
          "name": "hash"
        }
      ]
    }
  }
}`
)

func TestMain(m *testing.M) {
	defer cluster.PanicHandler(nil)
	flag.Parse()

	exitCode := func() int {
		clusterInstance = cluster.NewCluster(Cell, "localhost")
		defer clusterInstance.Teardown()

		// Start topo server
		err := clusterInstance.StartTopo()
		if err != nil {
			return 1
		}

		// Start keyspace
		keyspace := &cluster.Keyspace{
			Name:      KeyspaceName,
			SchemaSQL: SchemaSQL,
			VSchema:   VSchema,
		}
		clusterInstance.VtGateExtraArgs = []string{"-schema_change_signal", "-vschema_ddl_authorized_users", "%"}
		clusterInstance.VtTabletExtraArgs = []string{"-queryserver-config-schema-change-signal", "-queryserver-config-schema-change-signal-interval", "0.1"}
		err = clusterInstance.StartKeyspace(*keyspace, []string{"-80", "80-"}, 1, true)
		if err != nil {
			return 1
		}

		// Start vtgate
		err = clusterInstance.StartVtgate()
		if err != nil {
			return 1
		}

		vtParams = mysql.ConnParams{
			Host: clusterInstance.Hostname,
			Port: clusterInstance.VtgateMySQLPort,
		}
		return m.Run()
	}()
	os.Exit(exitCode)
}

func TestNewTable(t *testing.T) {
	ctx := context.Background()
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	shard1Params := vtParams
	shard1Params.DbName += ":-80@master"
	connShard1, err := mysql.Connect(ctx, &shard1Params)
	require.NoError(t, err)
	defer connShard1.Close()

	shard2Params := vtParams
	shard2Params.DbName += ":80-@master"
	connShard2, err := mysql.Connect(ctx, &shard2Params)
	require.NoError(t, err)
	defer connShard2.Close()

	_ = exec(t, conn, "create table test_table (id bigint, name varchar(100))")
	defer exec(t, conn, "drop table test_table")

	time.Sleep(2 * time.Second)

	assertMatches(t, conn, "select * from test_table", `[]`)
	assertMatches(t, connShard1, "select * from test_table", `[]`)
	assertMatches(t, connShard2, "select * from test_table", `[]`)
}

func TestAmbiguousColumnJoin(t *testing.T) {
	defer cluster.PanicHandler(t)
	ctx := context.Background()
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()
	// this query only works if we know which table the testId belongs to. The vschema does not contain
	// this info, so we are testing that the schema tracker has added column info to the vschema
	_, err = conn.ExecuteFetch(`select testId from t8 join t2`, 1000, true)
	require.NoError(t, err)
}

func TestInitAndUpdate(t *testing.T) {
	ctx := context.Background()
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	assertMatches(t, conn, "SHOW VSCHEMA TABLES", `[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]`)

	// Init
	_ = exec(t, conn, "create table test_sc (id bigint primary key)")
	assertMatchesWithTimeout(t, conn,
		"SHOW VSCHEMA TABLES",
		`[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")]]`,
		100*time.Millisecond,
		3*time.Second,
		"test_sc not in vschema tables")

	// Tables Update via health check.
	_ = exec(t, conn, "create table test_sc1 (id bigint primary key)")
	assertMatchesWithTimeout(t, conn,
		"SHOW VSCHEMA TABLES",
		`[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")] [VARCHAR("test_sc1")]]`,
		100*time.Millisecond,
		3*time.Second,
		"test_sc1 not in vschema tables")

	_ = exec(t, conn, "drop table test_sc, test_sc1")
	assertMatchesWithTimeout(t, conn,
		"SHOW VSCHEMA TABLES",
		`[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]`,
		100*time.Millisecond,
		3*time.Second,
		"test_sc and test_sc_1 should not be in vschema tables")

}

func TestDMLOnNewTable(t *testing.T) {
	ctx := context.Background()
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	// create a new table which is not part of the VSchema
	exec(t, conn, `create table new_table_tracked(id bigint, name varchar(100), primary key(id)) Engine=InnoDB`)

	// wait for vttablet's schema reload interval to pass
	assertMatchesWithTimeout(t, conn,
		"SHOW VSCHEMA TABLES",
		`[[VARCHAR("dual")] [VARCHAR("new_table_tracked")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]`,
		100*time.Millisecond,
		3*time.Second,
		"test_sc not in vschema tables")

	assertMatches(t, conn, "select id from new_table_tracked", `[]`)              // select
	assertMatches(t, conn, "select id from new_table_tracked where id = 5", `[]`) // select
	// DML on new table
	// insert initial data ,update and delete will fail since we have not added a primary vindex
	errorMessage := "table 'new_table_tracked' does not have a primary vindex (errno 1173) (sqlstate 42000)"
	assertError(t, conn, `insert into new_table_tracked(id) values(0),(1)`, errorMessage)
	assertError(t, conn, `update new_table_tracked set name = "newName1"`, errorMessage)
	assertError(t, conn, "delete from new_table_tracked", errorMessage)

	exec(t, conn, `select name from new_table_tracked join t8`)

	// add a primary vindex for the table
	exec(t, conn, "alter vschema on ks.new_table_tracked add vindex hash(id) using hash")
	time.Sleep(1 * time.Second)
	exec(t, conn, `insert into new_table_tracked(id) values(0),(1)`)
	exec(t, conn, `insert into t8(id8) values(2)`)
	defer exec(t, conn, `delete from t8`)
	assertMatchesNoOrder(t, conn, `select id from new_table_tracked join t8`, `[[INT64(0)] [INT64(1)]]`)
}

func assertMatches(t *testing.T, conn *mysql.Conn, query, expected string) {
	t.Helper()
	qr := exec(t, conn, query)
	got := fmt.Sprintf("%v", qr.Rows)
	diff := cmp.Diff(expected, got)
	if diff != "" {
		t.Errorf("Query: %s (-want +got):\n%s", query, diff)
	}
}

func assertMatchesWithTimeout(t *testing.T, conn *mysql.Conn, query, expected string, r time.Duration, d time.Duration, failureMsg string) {
	t.Helper()
	timeout := time.After(d)
	diff := "actual and expectation does not match"
	for len(diff) > 0 {
		select {
		case <-timeout:
			require.Fail(t, failureMsg, diff)
		case <-time.After(r):
			qr := exec(t, conn, query)
			diff = cmp.Diff(expected,
				fmt.Sprintf("%v", qr.Rows))
		}

	}
}

func assertMatchesNoOrder(t *testing.T, conn *mysql.Conn, query, expected string) {
	t.Helper()
	qr := exec(t, conn, query)
	actual := fmt.Sprintf("%v", qr.Rows)
	assert.Equal(t, utils.SortString(expected), utils.SortString(actual), "for query: [%s] expected \n%s \nbut actual \n%s", query, expected, actual)
}

func assertError(t *testing.T, conn *mysql.Conn, query, errorMessage string) {
	t.Helper()
	_, err := conn.ExecuteFetch(query, 1000, true)
	require.Error(t, err)
	assert.Contains(t, err.Error(), errorMessage)
}

func exec(t *testing.T, conn *mysql.Conn, query string) *sqltypes.Result {
	t.Helper()
	qr, err := conn.ExecuteFetch(query, 1000, true)
	require.NoError(t, err, "for query: "+query)
	return qr
}
<commit_msg>Wait after drop table to let schema tracking propagate the changes<commit_after>/*
Copyright 2021 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sharded

import (
	"context"
	"flag"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"

	"vitess.io/vitess/go/test/utils"

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

	"vitess.io/vitess/go/mysql"
	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/test/endtoend/cluster"
)

var (
	clusterInstance *cluster.LocalProcessCluster
	vtParams        mysql.ConnParams
	KeyspaceName    = "ks"
	Cell            = "test"
	SchemaSQL       = `
create table t2(
	id3 bigint,
	id4 bigint,
	primary key(id3)
) Engine=InnoDB;

create table t2_id4_idx(
	id bigint not null auto_increment,
	id4 bigint,
	id3 bigint,
	primary key(id),
	key idx_id4(id4)
) Engine=InnoDB;

create table t8(
	id8 bigint,
	testId bigint,
	primary key(id8)
) Engine=InnoDB;
`

	VSchema = `
{
  "sharded": true,
  "vindexes": {
    "unicode_loose_xxhash" : {
	  "type": "unicode_loose_xxhash"
    },
    "unicode_loose_md5" : {
	  "type": "unicode_loose_md5"
    },
    "hash": {
      "type": "hash"
    },
    "xxhash": {
      "type": "xxhash"
    },
    "t2_id4_idx": {
      "type": "lookup_hash",
      "params": {
        "table": "t2_id4_idx",
        "from": "id4",
        "to": "id3",
        "autocommit": "true"
      },
      "owner": "t2"
    }
  },
  "tables": {
    "t2": {
      "column_vindexes": [
        {
          "column": "id3",
          "name": "hash"
        },
        {
          "column": "id4",
          "name": "t2_id4_idx"
        }
      ]
    },
    "t2_id4_idx": {
      "column_vindexes": [
        {
          "column": "id4",
          "name": "hash"
        }
      ]
    },
    "t8": {
      "column_vindexes": [
        {
          "column": "id8",
          "name": "hash"
        }
      ]
    }
  }
}`
)

func TestMain(m *testing.M) {
	defer cluster.PanicHandler(nil)
	flag.Parse()

	exitCode := func() int {
		clusterInstance = cluster.NewCluster(Cell, "localhost")
		defer clusterInstance.Teardown()

		// Start topo server
		err := clusterInstance.StartTopo()
		if err != nil {
			return 1
		}

		// Start keyspace
		keyspace := &cluster.Keyspace{
			Name:      KeyspaceName,
			SchemaSQL: SchemaSQL,
			VSchema:   VSchema,
		}
		clusterInstance.VtGateExtraArgs = []string{"-schema_change_signal", "-vschema_ddl_authorized_users", "%"}
		clusterInstance.VtTabletExtraArgs = []string{"-queryserver-config-schema-change-signal", "-queryserver-config-schema-change-signal-interval", "0.1"}
		err = clusterInstance.StartKeyspace(*keyspace, []string{"-80", "80-"}, 1, true)
		if err != nil {
			return 1
		}

		// Start vtgate
		err = clusterInstance.StartVtgate()
		if err != nil {
			return 1
		}

		vtParams = mysql.ConnParams{
			Host: clusterInstance.Hostname,
			Port: clusterInstance.VtgateMySQLPort,
		}
		return m.Run()
	}()
	os.Exit(exitCode)
}

func TestNewTable(t *testing.T) {
	ctx := context.Background()
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	shard1Params := vtParams
	shard1Params.DbName += ":-80@master"
	connShard1, err := mysql.Connect(ctx, &shard1Params)
	require.NoError(t, err)
	defer connShard1.Close()

	shard2Params := vtParams
	shard2Params.DbName += ":80-@master"
	connShard2, err := mysql.Connect(ctx, &shard2Params)
	require.NoError(t, err)
	defer connShard2.Close()

	_ = exec(t, conn, "create table test_table (id bigint, name varchar(100))")

	time.Sleep(2 * time.Second)

	assertMatches(t, conn, "select * from test_table", `[]`)
	assertMatches(t, connShard1, "select * from test_table", `[]`)
	assertMatches(t, connShard2, "select * from test_table", `[]`)

	exec(t, conn, "drop table test_table")

	time.Sleep(2 * time.Second)
}

func TestAmbiguousColumnJoin(t *testing.T) {
	defer cluster.PanicHandler(t)
	ctx := context.Background()
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()
	// this query only works if we know which table the testId belongs to. The vschema does not contain
	// this info, so we are testing that the schema tracker has added column info to the vschema
	_, err = conn.ExecuteFetch(`select testId from t8 join t2`, 1000, true)
	require.NoError(t, err)
}

func TestInitAndUpdate(t *testing.T) {
	ctx := context.Background()
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	assertMatches(t, conn, "SHOW VSCHEMA TABLES", `[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]`)

	// Init
	_ = exec(t, conn, "create table test_sc (id bigint primary key)")
	assertMatchesWithTimeout(t, conn,
		"SHOW VSCHEMA TABLES",
		`[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")]]`,
		100*time.Millisecond,
		3*time.Second,
		"test_sc not in vschema tables")

	// Tables Update via health check.
	_ = exec(t, conn, "create table test_sc1 (id bigint primary key)")
	assertMatchesWithTimeout(t, conn,
		"SHOW VSCHEMA TABLES",
		`[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")] [VARCHAR("test_sc1")]]`,
		100*time.Millisecond,
		3*time.Second,
		"test_sc1 not in vschema tables")

	_ = exec(t, conn, "drop table test_sc, test_sc1")
	assertMatchesWithTimeout(t, conn,
		"SHOW VSCHEMA TABLES",
		`[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]`,
		100*time.Millisecond,
		3*time.Second,
		"test_sc and test_sc_1 should not be in vschema tables")

}

func TestDMLOnNewTable(t *testing.T) {
	ctx := context.Background()
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	// create a new table which is not part of the VSchema
	exec(t, conn, `create table new_table_tracked(id bigint, name varchar(100), primary key(id)) Engine=InnoDB`)

	// wait for vttablet's schema reload interval to pass
	assertMatchesWithTimeout(t, conn,
		"SHOW VSCHEMA TABLES",
		`[[VARCHAR("dual")] [VARCHAR("new_table_tracked")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]`,
		100*time.Millisecond,
		3*time.Second,
		"test_sc not in vschema tables")

	assertMatches(t, conn, "select id from new_table_tracked", `[]`)              // select
	assertMatches(t, conn, "select id from new_table_tracked where id = 5", `[]`) // select
	// DML on new table
	// insert initial data ,update and delete will fail since we have not added a primary vindex
	errorMessage := "table 'new_table_tracked' does not have a primary vindex (errno 1173) (sqlstate 42000)"
	assertError(t, conn, `insert into new_table_tracked(id) values(0),(1)`, errorMessage)
	assertError(t, conn, `update new_table_tracked set name = "newName1"`, errorMessage)
	assertError(t, conn, "delete from new_table_tracked", errorMessage)

	exec(t, conn, `select name from new_table_tracked join t8`)

	// add a primary vindex for the table
	exec(t, conn, "alter vschema on ks.new_table_tracked add vindex hash(id) using hash")
	time.Sleep(1 * time.Second)
	exec(t, conn, `insert into new_table_tracked(id) values(0),(1)`)
	exec(t, conn, `insert into t8(id8) values(2)`)
	defer exec(t, conn, `delete from t8`)
	assertMatchesNoOrder(t, conn, `select id from new_table_tracked join t8`, `[[INT64(0)] [INT64(1)]]`)
}

func assertMatches(t *testing.T, conn *mysql.Conn, query, expected string) {
	t.Helper()
	qr := exec(t, conn, query)
	got := fmt.Sprintf("%v", qr.Rows)
	diff := cmp.Diff(expected, got)
	if diff != "" {
		t.Errorf("Query: %s (-want +got):\n%s", query, diff)
	}
}

func assertMatchesWithTimeout(t *testing.T, conn *mysql.Conn, query, expected string, r time.Duration, d time.Duration, failureMsg string) {
	t.Helper()
	timeout := time.After(d)
	diff := "actual and expectation does not match"
	for len(diff) > 0 {
		select {
		case <-timeout:
			require.Fail(t, failureMsg, diff)
		case <-time.After(r):
			qr := exec(t, conn, query)
			diff = cmp.Diff(expected,
				fmt.Sprintf("%v", qr.Rows))
		}

	}
}

func assertMatchesNoOrder(t *testing.T, conn *mysql.Conn, query, expected string) {
	t.Helper()
	qr := exec(t, conn, query)
	actual := fmt.Sprintf("%v", qr.Rows)
	assert.Equal(t, utils.SortString(expected), utils.SortString(actual), "for query: [%s] expected \n%s \nbut actual \n%s", query, expected, actual)
}

func assertError(t *testing.T, conn *mysql.Conn, query, errorMessage string) {
	t.Helper()
	_, err := conn.ExecuteFetch(query, 1000, true)
	require.Error(t, err)
	assert.Contains(t, err.Error(), errorMessage)
}

func exec(t *testing.T, conn *mysql.Conn, query string) *sqltypes.Result {
	t.Helper()
	qr, err := conn.ExecuteFetch(query, 1000, true)
	require.NoError(t, err, "for query: "+query)
	return qr
}
<|endoftext|>


<commit_before>package network

import (
	"database/sql"

	"github.com/Nekitosss/authorization/network/controller"
	"github.com/Nekitosss/authorization/network/controller/structures"

	"github.com/satori/go.uuid"

	"github.com/Nekitosss/authorization/db"
)

var executor Executor

func PrepareExecutor(database *sql.DB, emailConfig EmailConfiguration) {
	executor = &ExecutorImpl{database, emailConfig}
}

type EmailConfiguration interface {

	GetGmailLogin() string

	GetGmailPassword() string

	GetDomain() string
}

type Executor interface {

	Register(info structures.RegisterInfo) error

	VerifyRegister(registerID uuid.UUID) error

	Login(info structures.LoginInfo) (*db.Session, error)

	ValidateSession(info structures.ValidateSessionInfo) (uuid.UUID, error)
}

type ExecutorImpl struct {
	database *sql.DB

	emailConfig EmailConfiguration
}

func (e *ExecutorImpl) Register(info structures.RegisterInfo) error {
	return controller.RegisterNewUser(e.database, info, e.emailConfig)
}

func (e *ExecutorImpl) VerifyRegister(registerID uuid.UUID) error {
	return controller.VerifyRegistration(e.database, registerID)
}

func (e *ExecutorImpl) Login(info structures.LoginInfo) (*db.Session, error) {
	return controller.LogIn(e.database, info)
}

func (e *ExecutorImpl) ValidateSession(info structures.ValidateSessionInfo) (uuid.UUID, error) {

	sid, err := uuid.FromString(info.SessionID)

	if err != nil {
		return uuid.Nil, err
	}

	return controller.ValidateSession(e.database, sid), nil
}<commit_msg>fixes<commit_after>package network

import (
	"github.com/jinzhu/gorm"

	"github.com/Nekitosss/authorization/network/controller"
	"github.com/Nekitosss/authorization/network/controller/structures"

	"github.com/satori/go.uuid"

	"github.com/Nekitosss/authorization/db"
)

var executor Executor

func PrepareExecutor(database *gorm.DB, emailConfig EmailConfiguration) {
	executor = &ExecutorImpl{database, emailConfig}
}

type EmailConfiguration interface {

	GetGmailLogin() string

	GetGmailPassword() string

	GetDomain() string
}

type Executor interface {

	Register(info structures.RegisterInfo) error

	VerifyRegister(registerID uuid.UUID) error

	Login(info structures.LoginInfo) (*db.Session, error)

	ValidateSession(info structures.ValidateSessionInfo) (uuid.UUID, error)
}

type ExecutorImpl struct {
	database *gorm.DB

	emailConfig EmailConfiguration
}

func (e *ExecutorImpl) Register(info structures.RegisterInfo) error {
	return controller.RegisterNewUser(e.database, info, e.emailConfig)
}

func (e *ExecutorImpl) VerifyRegister(registerID uuid.UUID) error {
	return controller.VerifyRegistration(e.database, registerID)
}

func (e *ExecutorImpl) Login(info structures.LoginInfo) (*db.Session, error) {
	return controller.LogIn(e.database, info)
}

func (e *ExecutorImpl) ValidateSession(info structures.ValidateSessionInfo) (uuid.UUID, error) {

	sid, err := uuid.FromString(info.SessionID)

	if err != nil {
		return uuid.Nil, err
	}

	return controller.ValidateSession(e.database, sid), nil
}<|endoftext|>


<commit_before>package main // import "sourcegraph.com/sourcegraph/gen-mocks"

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/imports"
)

var (
	importPath = flag.String("p", "", "package path")
	ifacePat   = flag.String("i", ".+Service", "regexp pattern for selecting interface types by name")
	writeFiles = flag.Bool("w", false, "write over existing files in output directory (default: writes to stdout)")
	outDir     = flag.String("o", "svc", "output directory")
	pkgName    = flag.String("n", "svc", "package name")

	fset = token.NewFileSet()
)

func main() {
	flag.Parse()
	log.SetFlags(0)
	if *importPath == "" {
		log.Fatal("Import path must be set")
	}

	pat, err := regexp.Compile(*ifacePat)
	if err != nil {
		log.Fatal(err)
	}

	ifacePkg, err := parseIfacePkg(*importPath)
	if err != nil {
		log.Fatal(err)
	}

	ifaces, err := readIfaces(ifacePkg, pat)
	if err != nil {
		log.Fatal(err)
	}

	if len(ifaces) == 0 {
		log.Printf("warning: package has no interface types matching %q", *ifacePat)
		return
	}

	if err := writeMockImplFiles(*outDir, ifacePkg.Name, ifaces); err != nil {
		log.Fatal(err)
	}
}

func parseIfacePkg(importPath string) (*ast.Package, error) {
	pkg, err := build.Import(importPath, "", 0)
	if err != nil {
		log.Fatal(err)
	}
	pkgs, err := parser.ParseDir(fset, pkg.Dir, nil, parser.AllErrors)
	if err != nil {
		log.Fatal(err)
	}
	for _, pkg := range pkgs {
		if pkg.Name == *pkgName {
			return pkg, nil
		}
	}
	return nil, fmt.Errorf("No %q package found in %s.", importPath, pkg.Dir)
}

// readIfaces returns a list of interface types in pkg that should be
// mocked.
func readIfaces(pkg *ast.Package, pat *regexp.Regexp) ([]*ast.TypeSpec, error) {
	var ifaces []*ast.TypeSpec
	ast.Walk(visitFn(func(node ast.Node) bool {
		switch node := node.(type) {
		case *ast.GenDecl:
			if node.Tok == token.TYPE {
				for _, spec := range node.Specs {
					tspec := spec.(*ast.TypeSpec)
					if _, ok := tspec.Type.(*ast.InterfaceType); !ok {
						continue
					}
					if name := tspec.Name.Name; pat.MatchString(name) {
						ifaces = append(ifaces, tspec)
					}
				}
			}
			return false
		default:
			return true
		}
	}), pkg)
	return ifaces, nil
}

type visitFn func(node ast.Node) (descend bool)

func (v visitFn) Visit(node ast.Node) ast.Visitor {
	descend := v(node)
	if descend {
		return v
	} else {
		return nil
	}
}

func writeMockImplFiles(outDir, pkgName string, svcIfaces []*ast.TypeSpec) error {
	if err := os.MkdirAll(outDir, 0700); err != nil {
		return err
	}
	for _, iface := range svcIfaces {
		var decls []ast.Decl

		// mock method fields on struct
		var methFields []*ast.Field
		for _, methField := range iface.Type.(*ast.InterfaceType).Methods.List {
			if meth, ok := methField.Type.(*ast.FuncType); ok {
				methFields = append(methFields, &ast.Field{
					Names: []*ast.Ident{ast.NewIdent(methField.Names[0].Name + "_")},
					Type:  meth,
				})
			}
		}

		// struct implementation type
		mockTypeName := "Mock" + iface.Name.Name
		implType := &ast.GenDecl{Tok: token.TYPE, Specs: []ast.Spec{&ast.TypeSpec{
			Name: ast.NewIdent(mockTypeName),
			Type: &ast.StructType{Fields: &ast.FieldList{List: methFields}},
		}}}
		decls = append(decls, implType)

		// struct methods
		for _, methField := range iface.Type.(*ast.InterfaceType).Methods.List {
			if meth, ok := methField.Type.(*ast.FuncType); ok {
				decls = append(decls, &ast.FuncDecl{
					Recv: &ast.FieldList{List: []*ast.Field{
						{
							Names: []*ast.Ident{ast.NewIdent("s")},
							Type:  &ast.StarExpr{X: ast.NewIdent(mockTypeName)},
						},
					}},
					Name: ast.NewIdent(methField.Names[0].Name),
					Type: meth,
					Body: &ast.BlockStmt{List: []ast.Stmt{
						&ast.ReturnStmt{Results: []ast.Expr{
							&ast.CallExpr{
								Fun: &ast.SelectorExpr{
									X:   ast.NewIdent("s"),
									Sel: ast.NewIdent(methField.Names[0].Name + "_"),
								},
								Args: fieldListToIdentList(meth.Params),
							},
						}},
					}},
				})
			}
		}

		file := &ast.File{
			Name:  ast.NewIdent(pkgName),
			Decls: decls,
		}
		astutil.AddImport(fset, file, *importPath)
		filename := fset.Position(iface.Pos()).Filename
		filename = filepath.Join(outDir, strings.TrimSuffix(filepath.Base(filename), ".go")+"_mock.go")
		log.Println("#", filename)
		var w io.Writer
		if *writeFiles {
			f, err := os.Create(filename)
			if err != nil {
				return err
			}
			defer f.Close()
			w = f
		} else {
			w = os.Stdout
		}

		var buf bytes.Buffer
		if err := printer.Fprint(&buf, fset, file); err != nil {
			return err
		}

		// Always put blank lines between funcs.
		src := bytes.Replace(buf.Bytes(), []byte("}\nfunc"), []byte("}\n\nfunc"), -1)

		var err error
		src, err = imports.Process(filename, src, nil)
		if err != nil {
			return err
		}

		w.Write(src)
	}
	return nil
}

func fieldListToIdentList(fl *ast.FieldList) []ast.Expr {
	var fs []ast.Expr
	for _, f := range fl.List {
		for _, name := range f.Names {
			fs = append(fs, ast.NewIdent(name.Name))
		}
	}
	return fs
}
<commit_msg>synthesize field names if missing<commit_after>package main // import "sourcegraph.com/sourcegraph/gen-mocks"

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/imports"
)

var (
	importPath = flag.String("p", "", "package path")
	ifacePat   = flag.String("i", ".+Service", "regexp pattern for selecting interface types by name")
	writeFiles = flag.Bool("w", false, "write over existing files in output directory (default: writes to stdout)")
	outDir     = flag.String("o", "svc", "output directory")
	pkgName    = flag.String("n", "svc", "package name")

	fset = token.NewFileSet()
)

func main() {
	flag.Parse()
	log.SetFlags(0)
	if *importPath == "" {
		log.Fatal("Import path must be set")
	}

	pat, err := regexp.Compile(*ifacePat)
	if err != nil {
		log.Fatal(err)
	}

	ifacePkg, err := parseIfacePkg(*importPath)
	if err != nil {
		log.Fatal(err)
	}

	ifaces, err := readIfaces(ifacePkg, pat)
	if err != nil {
		log.Fatal(err)
	}

	if len(ifaces) == 0 {
		log.Printf("warning: package has no interface types matching %q", *ifacePat)
		return
	}

	if err := writeMockImplFiles(*outDir, ifacePkg.Name, ifaces); err != nil {
		log.Fatal(err)
	}
}

func parseIfacePkg(importPath string) (*ast.Package, error) {
	pkg, err := build.Import(importPath, "", 0)
	if err != nil {
		log.Fatal(err)
	}
	pkgs, err := parser.ParseDir(fset, pkg.Dir, nil, parser.AllErrors)
	if err != nil {
		log.Fatal(err)
	}
	for _, pkg := range pkgs {
		if pkg.Name == *pkgName {
			return pkg, nil
		}
	}
	return nil, fmt.Errorf("No %q package found in %s.", importPath, pkg.Dir)
}

// readIfaces returns a list of interface types in pkg that should be
// mocked.
func readIfaces(pkg *ast.Package, pat *regexp.Regexp) ([]*ast.TypeSpec, error) {
	var ifaces []*ast.TypeSpec
	ast.Walk(visitFn(func(node ast.Node) bool {
		switch node := node.(type) {
		case *ast.GenDecl:
			if node.Tok == token.TYPE {
				for _, spec := range node.Specs {
					tspec := spec.(*ast.TypeSpec)
					if _, ok := tspec.Type.(*ast.InterfaceType); !ok {
						continue
					}
					if name := tspec.Name.Name; pat.MatchString(name) {
						ifaces = append(ifaces, tspec)
					}
				}
			}
			return false
		default:
			return true
		}
	}), pkg)
	return ifaces, nil
}

type visitFn func(node ast.Node) (descend bool)

func (v visitFn) Visit(node ast.Node) ast.Visitor {
	descend := v(node)
	if descend {
		return v
	} else {
		return nil
	}
}

func writeMockImplFiles(outDir, pkgName string, svcIfaces []*ast.TypeSpec) error {
	if err := os.MkdirAll(outDir, 0700); err != nil {
		return err
	}
	for _, iface := range svcIfaces {
		var decls []ast.Decl

		// mock method fields on struct
		var methFields []*ast.Field
		for _, methField := range iface.Type.(*ast.InterfaceType).Methods.List {
			if meth, ok := methField.Type.(*ast.FuncType); ok {
				methFields = append(methFields, &ast.Field{
					Names: []*ast.Ident{ast.NewIdent(methField.Names[0].Name + "_")},
					Type:  meth,
				})
			}
		}

		// struct implementation type
		mockTypeName := "Mock" + iface.Name.Name
		implType := &ast.GenDecl{Tok: token.TYPE, Specs: []ast.Spec{&ast.TypeSpec{
			Name: ast.NewIdent(mockTypeName),
			Type: &ast.StructType{Fields: &ast.FieldList{List: methFields}},
		}}}
		decls = append(decls, implType)

		// struct methods
		for _, methField := range iface.Type.(*ast.InterfaceType).Methods.List {
			if meth, ok := methField.Type.(*ast.FuncType); ok {
				synthesizeFieldNamesIfMissing(meth.Params)
				decls = append(decls, &ast.FuncDecl{
					Recv: &ast.FieldList{List: []*ast.Field{
						{
							Names: []*ast.Ident{ast.NewIdent("s")},
							Type:  &ast.StarExpr{X: ast.NewIdent(mockTypeName)},
						},
					}},
					Name: ast.NewIdent(methField.Names[0].Name),
					Type: meth,
					Body: &ast.BlockStmt{List: []ast.Stmt{
						&ast.ReturnStmt{Results: []ast.Expr{
							&ast.CallExpr{
								Fun: &ast.SelectorExpr{
									X:   ast.NewIdent("s"),
									Sel: ast.NewIdent(methField.Names[0].Name + "_"),
								},
								Args: fieldListToIdentList(meth.Params),
							},
						}},
					}},
				})
			}
		}

		file := &ast.File{
			Name:  ast.NewIdent(pkgName),
			Decls: decls,
		}
		astutil.AddImport(fset, file, *importPath)
		filename := fset.Position(iface.Pos()).Filename
		filename = filepath.Join(outDir, strings.TrimSuffix(filepath.Base(filename), ".go")+"_mock.go")
		log.Println("#", filename)
		var w io.Writer
		if *writeFiles {
			f, err := os.Create(filename)
			if err != nil {
				return err
			}
			defer f.Close()
			w = f
		} else {
			w = os.Stdout
		}

		var buf bytes.Buffer
		if err := printer.Fprint(&buf, fset, file); err != nil {
			return err
		}

		// Always put blank lines between funcs.
		src := bytes.Replace(buf.Bytes(), []byte("}\nfunc"), []byte("}\n\nfunc"), -1)

		var err error
		src, err = imports.Process(filename, src, nil)
		if err != nil {
			return err
		}

		w.Write(src)
	}
	return nil
}

// synthesizeFieldNamesIfMissing adds synthesized variable names to fl
// if it contains fields with no name. E.g., the field list in
// `func(string, int)` would be converted to `func(v0 string, v1
// int)`.
func synthesizeFieldNamesIfMissing(fl *ast.FieldList) {
	for i, f := range fl.List {
		if len(f.Names) == 0 {
			f.Names = []*ast.Ident{ast.NewIdent(fmt.Sprintf("v%d", i))}
		}
	}
}

func fieldListToIdentList(fl *ast.FieldList) []ast.Expr {
	var fs []ast.Expr
	for _, f := range fl.List {
		for _, name := range f.Names {
			fs = append(fs, ast.NewIdent(name.Name))
		}
	}
	return fs
}
<|endoftext|>


<commit_before>package ctlr

import (
	"fmt"
	"github.com/aybabtme/goblog/db"
	"net/http"
)

func NewIndexController() Controller {
	return new(index)
}

type index string

func (i *index) Path() string {
	return "/"
}

func (i *index) Controller(conn *db.DBConnection) func(http.ResponseWriter, *http.Request) {
	return func(rw http.ResponseWriter, req *http.Request) {

		//

	}
}
<commit_msg>Fix the fmt bug.<commit_after>package ctlr

import (
	"fmt"
	"github.com/aybabtme/goblog/db"
	"net/http"
)

func NewIndexController() Controller {
	return new(index)
}

type index string

func (i *index) Path() string {
	return "/"
}

func (i *index) Controller(conn *db.DBConnection) func(http.ResponseWriter, *http.Request) {
	return func(rw http.ResponseWriter, req *http.Request) {

		fmt.Fprintf(rw, "this is the index page!")

	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path"

	"code.google.com/p/goauth2/oauth"

	flag "github.com/docker/docker/pkg/mflag"
	"github.com/google/go-github/github"
)

type Config struct {
	BuildFlags []string `json:"build_flags,omitempty"`
	AuthToken  string   `json:"auth_token"`
}

// initialize flags
var (
	// Config needs to be initted in init()
	ConfigPath  *string
	BuildFlags  = flag.String([]string{"b", "-buildflags"}, "", "Pass build flags to gox")
	ForceCreate = flag.Bool([]string{"f", "-force"}, false, "Replace the asset if it already exists")
	AssetName   = flag.String([]string{"n", "-name"}, "", "Set the name for the release asset")
	Prerelease  = flag.Bool([]string{"p", "-prerelease"}, false, "With -create, create as a dev release")
	ReleaseName = flag.String([]string{"r", "-release"}, "", "Create the release if it does not exist and set the release name")
	Version     = flag.Bool([]string{"v", "-version"}, false, "Print the name and version")
	GoxPath     *string
)

func init() {
	CurrentUser, _ := user.Current()
	DefaultConfigPath := path.Join(CurrentUser.HomeDir, ".config/ghrelease/config.json")
	ConfigPath = flag.String([]string{"c", "-config"}, DefaultConfigPath, "Set ghrelease config file")
	GoxPath = flag.String([]string{"g", "-gox"}, *FindGox(), "Path to gox")

	flag.Parse()
}

func LoadConfig(ConfigPath *string) Config {
	var ret Config
	ConfigFile, err := ioutil.ReadFile(*ConfigPath)
	err = json.Unmarshal(ConfigFile, &ret)
	if err != nil {
		log.Fatalln(err)
	}
	return ret
}

func FindGox() *string {
	Path, err := exec.LookPath("gox")
	if err != nil {
		log.Fatalln("gox is not in your $PATH")
	}
	return &Path
}

func Gox(BuildFlags *string) *string {
	cmd := exec.Command(*GoxPath, *BuildFlags)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Fatalln(err)
	}
	output := out.String()
	return &output
}

func main() {
	GHRConfig := LoadConfig(ConfigPath)

	EnvAuthToken := os.Getenv("GHRELEASE_AUTH_TOKEN")
	if EnvAuthToken != "" {
		GHRConfig.AuthToken = EnvAuthToken
	}
	if GHRConfig.AuthToken == "" {
		log.Println("auth_token is required to upload release assets to GitHub")
	}

	transport := &oauth.Transport{
		Token: &oauth.Token{AccessToken: GHRConfig.AuthToken},
	}
	client := github.NewClient(transport.Client())

	BuildOutput := Gox(BuildFlags)
}
<commit_msg>return pointer to Config<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path"

	"code.google.com/p/goauth2/oauth"

	flag "github.com/docker/docker/pkg/mflag"
	"github.com/google/go-github/github"
)

type Config struct {
	BuildFlags []string `json:"build_flags,omitempty"`
	AuthToken  string   `json:"auth_token"`
}

// initialize flags
var (
	// Config needs to be initted in init()
	ConfigPath  *string
	BuildFlags  = flag.String([]string{"b", "-buildflags"}, "", "Pass build flags to gox")
	ForceCreate = flag.Bool([]string{"f", "-force"}, false, "Replace the asset if it already exists")
	AssetName   = flag.String([]string{"n", "-name"}, "", "Set the name for the release asset")
	Prerelease  = flag.Bool([]string{"p", "-prerelease"}, false, "With -create, create as a dev release")
	ReleaseName = flag.String([]string{"r", "-release"}, "", "Create the release if it does not exist and set the release name")
	Version     = flag.Bool([]string{"v", "-version"}, false, "Print the name and version")
	GoxPath     *string
)

func init() {
	CurrentUser, _ := user.Current()
	DefaultConfigPath := path.Join(CurrentUser.HomeDir, ".config/ghrelease/config.json")
	ConfigPath = flag.String([]string{"c", "-config"}, DefaultConfigPath, "Set ghrelease config file")
	GoxPath = flag.String([]string{"g", "-gox"}, *FindGox(), "Path to gox")

	flag.Parse()
}

func LoadConfig(ConfigPath *string) *Config {
	var ret Config
	ConfigFile, err := ioutil.ReadFile(*ConfigPath)
	err = json.Unmarshal(ConfigFile, &ret)
	if err != nil {
		log.Fatalln(err)
	}
	return &ret
}

func FindGox() *string {
	Path, err := exec.LookPath("gox")
	if err != nil {
		log.Fatalln("gox is not in your $PATH")
	}
	return &Path
}

func Gox(BuildFlags *string) *string {
	cmd := exec.Command(*GoxPath, *BuildFlags)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Fatalln(err)
	}
	output := out.String()
	return &output
}

func main() {
	GHRConfig := LoadConfig(ConfigPath)

	EnvAuthToken := os.Getenv("GHRELEASE_AUTH_TOKEN")
	if EnvAuthToken != "" {
		GHRConfig.AuthToken = EnvAuthToken
	}
	if GHRConfig.AuthToken == "" {
		log.Println("auth_token is required to upload release assets to GitHub")
	}

	transport := &oauth.Transport{
		Token: &oauth.Token{AccessToken: GHRConfig.AuthToken},
	}
	client := github.NewClient(transport.Client())

	BuildOutput := Gox(BuildFlags)
}
<|endoftext|>


<commit_before>package edit

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"sort"
	"strings"

	"github.com/elves/elvish/eval"
	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/util"
)

// completer takes the current Node (always a leaf in the AST) and an Editor and
// returns a compl. If the completer does not apply to the type of the current
// Node, it should return an error of ErrCompletionUnapplicable.
type completer func(parse.Node, *eval.Evaler) (*compl, error)

// compl is the result of a completer, meaning that any of the candidates can
// replace the text in the interval [begin, end).
type compl struct {
	begin int
	end   int
	cands []*candidate
}

var errCompletionUnapplicable = errors.New("completion unapplicable")

// completers is the list of all completers.
// TODO(xiaq): Make this list programmable.
var completers = []struct {
	name string
	completer
}{
	{"variable", complVariable},
	{"command name", complFormHead},
	{"argument", complArg},
}

func complVariable(n parse.Node, ev *eval.Evaler) (*compl, error) {
	primary := parse.GetPrimary(n)
	if primary == nil || primary.Type != parse.Variable {
		return nil, errCompletionUnapplicable
	}

	// XXX Repeats eval.ParseVariable.
	explode, qname := eval.ParseVariableSplice(primary.Value)
	nsPart, nameHead := eval.ParseVariableQName(qname)
	ns := nsPart
	if len(ns) > 0 {
		ns = ns[:len(ns)-1]
	}

	// Collect matching variables.
	var varnames []string
	iterateVariables(ev, ns, func(varname string) {
		if strings.HasPrefix(varname, nameHead) {
			varnames = append(varnames, nsPart+varname)
		}
	})
	// Collect namespace prefixes.
	// TODO Support non-module namespaces.
	for ns := range ev.Modules {
		if hasProperPrefix(ns+":", qname) {
			varnames = append(varnames, ns+":")
		}
	}
	sort.Strings(varnames)

	cands := make([]*candidate, len(varnames))
	// Build candidates.
	for i, varname := range varnames {
		cands[i] = newPlainCandidate("$" + explode + varname)
	}
	return &compl{n.Begin(), n.End(), cands}, nil
}

func hasProperPrefix(s, p string) bool {
	return len(s) > len(p) && strings.HasPrefix(s, p)
}

func iterateVariables(ev *eval.Evaler, ns string, f func(string)) {
	switch ns {
	case "":
		for varname := range eval.Builtin() {
			f(varname)
		}
		for varname := range ev.Global {
			f(varname)
		}
		// TODO Include local names as well.
	case "E":
		for _, s := range os.Environ() {
			f(s[:strings.IndexByte(s, '=')])
		}
	default:
		// TODO Support non-module namespaces.
		for varname := range ev.Modules[ns] {
			f(varname)
		}
	}
}

func complFormHead(n parse.Node, ev *eval.Evaler) (*compl, error) {
	begin, end, head, q := findFormHeadContext(n)
	if begin == -1 {
		return nil, errCompletionUnapplicable
	}
	cands, err := complFormHeadInner(head, ev)
	if err != nil {
		return nil, err
	}
	quoteCandidates(cands, q)
	return &compl{begin, end, cands}, nil
}

func findFormHeadContext(n parse.Node) (int, int, string, parse.PrimaryType) {
	// Determine if we are starting a new command. There are 3 cases:
	// 1. The whole chunk is empty (nothing entered at all): the leaf is a
	//    Chunk.
	// 2. Just after a newline or semicolon: the leaf is a Sep and its parent is
	//    a Chunk.
	// 3. Just after a pipe: the leaf is a Sep and its parent is a Pipeline.
	if parse.IsChunk(n) {
		return n.End(), n.End(), "", parse.Bareword
	}
	if parse.IsSep(n) {
		parent := n.Parent()
		if parse.IsChunk(parent) || parse.IsPipeline(parent) {
			return n.End(), n.End(), "", parse.Bareword
		}
	}

	if primary, ok := n.(*parse.Primary); ok {
		if compound, head := primaryInSimpleCompound(primary); compound != nil {
			if form, ok := compound.Parent().(*parse.Form); ok {
				if form.Head == compound {
					return compound.Begin(), compound.End(), head, primary.Type
				}
			}
		}
	}
	return -1, -1, "", 0
}

func complFormHeadInner(head string, ev *eval.Evaler) ([]*candidate, error) {
	if util.DontSearch(head) {
		return complFilenameInner(head, true)
	}

	var commands []string
	got := func(s string) {
		if strings.HasPrefix(s, head) {
			commands = append(commands, s)
		}
	}
	for special := range isBuiltinSpecial {
		got(special)
	}
	explode, ns, _ := eval.ParseVariable(head)
	if !explode {
		iterateVariables(ev, ns, func(varname string) {
			if strings.HasPrefix(varname, eval.FnPrefix) {
				got(eval.MakeVariableName(false, ns, varname[len(eval.FnPrefix):]))
			} else {
				got(eval.MakeVariableName(false, ns, varname) + "=")
			}
		})
	}
	ev.EachExternal(func(command string) {
		got(command)
		if strings.HasPrefix(head, "e:") {
			got("e:" + command)
		}
	})
	// TODO Support non-module namespaces.
	for ns := range ev.Modules {
		if head != ns+":" {
			got(ns + ":")
		}
	}
	sort.Strings(commands)

	cands := []*candidate{}
	for _, cmd := range commands {
		cands = append(cands, newPlainCandidate(cmd))
	}
	return cands, nil
}

// complArg completes arguments. It identifies the context and then delegates
// the actual completion work to a suitable completer.
func complArg(n parse.Node, ev *eval.Evaler) (*compl, error) {
	begin, end, current, q, form := findArgContext(n)
	if begin == -1 {
		return nil, errCompletionUnapplicable
	}

	// Find out head of the form and preceding arguments.
	// If Form.Head is not a simple compound, head will be "", just what we want.
	_, head, _ := simpleCompound(form.Head, nil)
	var args []string
	for _, compound := range form.Args {
		if compound.Begin() >= begin {
			break
		}
		ok, arg, _ := simpleCompound(compound, nil)
		if ok {
			// XXX Arguments that are not simple compounds are simply ignored.
			args = append(args, arg)
		}
	}

	words := make([]string, len(args)+2)
	words[0] = head
	words[len(words)-1] = current
	copy(words[1:len(words)-1], args[:])

	cands, err := completeArg(words, ev)
	if err != nil {
		return nil, err
	}
	quoteCandidates(cands, q)
	return &compl{begin, end, cands}, nil
}

func findArgContext(n parse.Node) (int, int, string, parse.PrimaryType, *parse.Form) {
	if sep, ok := n.(*parse.Sep); ok {
		if form, ok := sep.Parent().(*parse.Form); ok {
			return n.End(), n.End(), "", parse.Bareword, form
		}
	}
	if primary, ok := n.(*parse.Primary); ok {
		if compound, head := primaryInSimpleCompound(primary); compound != nil {
			if form, ok := compound.Parent().(*parse.Form); ok {
				if form.Head != compound {
					return compound.Begin(), compound.End(), head, primary.Type, form
				}
			}
		}
	}
	return -1, -1, "", 0, nil
}

// TODO: getStyle does redundant stats.
func complFilenameInner(head string, executableOnly bool) ([]*candidate, error) {
	dir, fileprefix := path.Split(head)
	if dir == "" {
		dir = "."
	}

	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, fmt.Errorf("cannot list directory %s: %v", dir, err)
	}

	cands := []*candidate{}
	lsColor := getLsColor()
	// Make candidates out of elements that match the file component.
	for _, info := range infos {
		name := info.Name()
		// Irrevelant file.
		if !strings.HasPrefix(name, fileprefix) {
			continue
		}
		// Hide dot files unless file starts with a dot.
		if !dotfile(fileprefix) && dotfile(name) {
			continue
		}
		// Only accept searchable directories and executable files if
		// executableOnly is true.
		if executableOnly && !(info.IsDir() || (info.Mode()&0111) != 0) {
			continue
		}

		// Full filename for source and getStyle.
		full := head + name[len(fileprefix):]

		suffix := " "
		if info.IsDir() {
			suffix = "/"
		} else if info.Mode()&os.ModeSymlink != 0 {
			stat, err := os.Stat(full)
			if err == nil && stat.IsDir() {
				// Symlink to directory.
				suffix = "/"
			}
		}

		cands = append(cands, &candidate{
			text: full, suffix: suffix,
			display: styled{name, stylesFromString(lsColor.getStyle(full))},
		})
	}

	return cands, nil
}

func quoteCandidates(cands []*candidate, q parse.PrimaryType) []*candidate {
	for _, cand := range cands {
		quoted, _ := parse.QuoteAs(cand.text, q)
		cand.text = quoted + cand.suffix
	}
	return cands
}

func dotfile(fname string) bool {
	return strings.HasPrefix(fname, ".")
}
<commit_msg>Support completing redir RHS.<commit_after>package edit

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"sort"
	"strings"

	"github.com/elves/elvish/eval"
	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/util"
)

// completer takes the current Node (always a leaf in the AST) and an Editor and
// returns a compl. If the completer does not apply to the type of the current
// Node, it should return an error of ErrCompletionUnapplicable.
type completer func(parse.Node, *eval.Evaler) (*compl, error)

// compl is the result of a completer, meaning that any of the candidates can
// replace the text in the interval [begin, end).
type compl struct {
	begin int
	end   int
	cands []*candidate
}

var errCompletionUnapplicable = errors.New("completion unapplicable")

// completers is the list of all completers.
// TODO(xiaq): Make this list programmable.
var completers = []struct {
	name string
	completer
}{
	{"variable", complVariable},
	{"command name", complFormHead},
	{"redir", complRedir},
	{"argument", complArg},
}

func complVariable(n parse.Node, ev *eval.Evaler) (*compl, error) {
	primary := parse.GetPrimary(n)
	if primary == nil || primary.Type != parse.Variable {
		return nil, errCompletionUnapplicable
	}

	// XXX Repeats eval.ParseVariable.
	explode, qname := eval.ParseVariableSplice(primary.Value)
	nsPart, nameHead := eval.ParseVariableQName(qname)
	ns := nsPart
	if len(ns) > 0 {
		ns = ns[:len(ns)-1]
	}

	// Collect matching variables.
	var varnames []string
	iterateVariables(ev, ns, func(varname string) {
		if strings.HasPrefix(varname, nameHead) {
			varnames = append(varnames, nsPart+varname)
		}
	})
	// Collect namespace prefixes.
	// TODO Support non-module namespaces.
	for ns := range ev.Modules {
		if hasProperPrefix(ns+":", qname) {
			varnames = append(varnames, ns+":")
		}
	}
	sort.Strings(varnames)

	cands := make([]*candidate, len(varnames))
	// Build candidates.
	for i, varname := range varnames {
		cands[i] = newPlainCandidate("$" + explode + varname)
	}
	return &compl{n.Begin(), n.End(), cands}, nil
}

func hasProperPrefix(s, p string) bool {
	return len(s) > len(p) && strings.HasPrefix(s, p)
}

func iterateVariables(ev *eval.Evaler, ns string, f func(string)) {
	switch ns {
	case "":
		for varname := range eval.Builtin() {
			f(varname)
		}
		for varname := range ev.Global {
			f(varname)
		}
		// TODO Include local names as well.
	case "E":
		for _, s := range os.Environ() {
			f(s[:strings.IndexByte(s, '=')])
		}
	default:
		// TODO Support non-module namespaces.
		for varname := range ev.Modules[ns] {
			f(varname)
		}
	}
}

func complFormHead(n parse.Node, ev *eval.Evaler) (*compl, error) {
	begin, end, head, q := findFormHeadContext(n)
	if begin == -1 {
		return nil, errCompletionUnapplicable
	}
	cands, err := complFormHeadInner(head, ev)
	if err != nil {
		return nil, err
	}
	quoteCandidates(cands, q)
	return &compl{begin, end, cands}, nil
}

func findFormHeadContext(n parse.Node) (int, int, string, parse.PrimaryType) {
	// Determine if we are starting a new command. There are 3 cases:
	// 1. The whole chunk is empty (nothing entered at all): the leaf is a
	//    Chunk.
	// 2. Just after a newline or semicolon: the leaf is a Sep and its parent is
	//    a Chunk.
	// 3. Just after a pipe: the leaf is a Sep and its parent is a Pipeline.
	if parse.IsChunk(n) {
		return n.End(), n.End(), "", parse.Bareword
	}
	if parse.IsSep(n) {
		parent := n.Parent()
		if parse.IsChunk(parent) || parse.IsPipeline(parent) {
			return n.End(), n.End(), "", parse.Bareword
		}
	}

	if primary, ok := n.(*parse.Primary); ok {
		if compound, head := primaryInSimpleCompound(primary); compound != nil {
			if form, ok := compound.Parent().(*parse.Form); ok {
				if form.Head == compound {
					return compound.Begin(), compound.End(), head, primary.Type
				}
			}
		}
	}
	return -1, -1, "", 0
}

func complFormHeadInner(head string, ev *eval.Evaler) ([]*candidate, error) {
	if util.DontSearch(head) {
		return complFilenameInner(head, true)
	}

	var commands []string
	got := func(s string) {
		if strings.HasPrefix(s, head) {
			commands = append(commands, s)
		}
	}
	for special := range isBuiltinSpecial {
		got(special)
	}
	explode, ns, _ := eval.ParseVariable(head)
	if !explode {
		iterateVariables(ev, ns, func(varname string) {
			if strings.HasPrefix(varname, eval.FnPrefix) {
				got(eval.MakeVariableName(false, ns, varname[len(eval.FnPrefix):]))
			} else {
				got(eval.MakeVariableName(false, ns, varname) + "=")
			}
		})
	}
	ev.EachExternal(func(command string) {
		got(command)
		if strings.HasPrefix(head, "e:") {
			got("e:" + command)
		}
	})
	// TODO Support non-module namespaces.
	for ns := range ev.Modules {
		if head != ns+":" {
			got(ns + ":")
		}
	}
	sort.Strings(commands)

	cands := []*candidate{}
	for _, cmd := range commands {
		cands = append(cands, newPlainCandidate(cmd))
	}
	return cands, nil
}

// complRedir completes redirection RHS.
func complRedir(n parse.Node, ev *eval.Evaler) (*compl, error) {
	begin, end, current, q := findRedirContext(n)
	if begin == -1 {
		return nil, errCompletionUnapplicable
	}
	cands, err := complFilenameInner(current, false)
	if err != nil {
		return nil, err
	}
	quoteCandidates(cands, q)
	return &compl{begin, end, cands}, nil
}

func findRedirContext(n parse.Node) (int, int, string, parse.PrimaryType) {
	if parse.IsSep(n) {
		if parse.IsRedir(n.Parent()) {
			return n.End(), n.End(), "", parse.Bareword
		}
	}
	if primary, ok := n.(*parse.Primary); ok {
		if compound, head := primaryInSimpleCompound(primary); compound != nil {
			if parse.IsRedir(compound.Parent()) {
				return compound.Begin(), compound.End(), head, primary.Type
			}
		}
	}
	return -1, -1, "", 0
}

// complArg completes arguments. It identifies the context and then delegates
// the actual completion work to a suitable completer.
func complArg(n parse.Node, ev *eval.Evaler) (*compl, error) {
	begin, end, current, q, form := findArgContext(n)
	if begin == -1 {
		return nil, errCompletionUnapplicable
	}

	// Find out head of the form and preceding arguments.
	// If Form.Head is not a simple compound, head will be "", just what we want.
	_, head, _ := simpleCompound(form.Head, nil)
	var args []string
	for _, compound := range form.Args {
		if compound.Begin() >= begin {
			break
		}
		ok, arg, _ := simpleCompound(compound, nil)
		if ok {
			// XXX Arguments that are not simple compounds are simply ignored.
			args = append(args, arg)
		}
	}

	words := make([]string, len(args)+2)
	words[0] = head
	words[len(words)-1] = current
	copy(words[1:len(words)-1], args[:])

	cands, err := completeArg(words, ev)
	if err != nil {
		return nil, err
	}
	quoteCandidates(cands, q)
	return &compl{begin, end, cands}, nil
}

func findArgContext(n parse.Node) (int, int, string, parse.PrimaryType, *parse.Form) {
	if sep, ok := n.(*parse.Sep); ok {
		if form, ok := sep.Parent().(*parse.Form); ok {
			return n.End(), n.End(), "", parse.Bareword, form
		}
	}
	if primary, ok := n.(*parse.Primary); ok {
		if compound, head := primaryInSimpleCompound(primary); compound != nil {
			if form, ok := compound.Parent().(*parse.Form); ok {
				if form.Head != compound {
					return compound.Begin(), compound.End(), head, primary.Type, form
				}
			}
		}
	}
	return -1, -1, "", 0, nil
}

// TODO: getStyle does redundant stats.
func complFilenameInner(head string, executableOnly bool) ([]*candidate, error) {
	dir, fileprefix := path.Split(head)
	if dir == "" {
		dir = "."
	}

	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, fmt.Errorf("cannot list directory %s: %v", dir, err)
	}

	cands := []*candidate{}
	lsColor := getLsColor()
	// Make candidates out of elements that match the file component.
	for _, info := range infos {
		name := info.Name()
		// Irrevelant file.
		if !strings.HasPrefix(name, fileprefix) {
			continue
		}
		// Hide dot files unless file starts with a dot.
		if !dotfile(fileprefix) && dotfile(name) {
			continue
		}
		// Only accept searchable directories and executable files if
		// executableOnly is true.
		if executableOnly && !(info.IsDir() || (info.Mode()&0111) != 0) {
			continue
		}

		// Full filename for source and getStyle.
		full := head + name[len(fileprefix):]

		suffix := " "
		if info.IsDir() {
			suffix = "/"
		} else if info.Mode()&os.ModeSymlink != 0 {
			stat, err := os.Stat(full)
			if err == nil && stat.IsDir() {
				// Symlink to directory.
				suffix = "/"
			}
		}

		cands = append(cands, &candidate{
			text: full, suffix: suffix,
			display: styled{name, stylesFromString(lsColor.getStyle(full))},
		})
	}

	return cands, nil
}

func quoteCandidates(cands []*candidate, q parse.PrimaryType) []*candidate {
	for _, cand := range cands {
		quoted, _ := parse.QuoteAs(cand.text, q)
		cand.text = quoted + cand.suffix
	}
	return cands
}

func dotfile(fname string) bool {
	return strings.HasPrefix(fname, ".")
}
<|endoftext|>


<commit_before>package edit

import (
	"errors"
	"os"
	"path"
	"strings"
	"unicode/utf8"

	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/util"
)

// Navigation subsystem.

// Interface.

type navigation struct {
	current    *navColumn
	parent     *navColumn
	dirPreview *navColumn
	showHidden bool
	filtering  bool
	filter     string
}

func (*navigation) Mode() ModeType {
	return modeNavigation
}

func (n *navigation) ModeLine(width int) *buffer {
	s := " NAVIGATING "
	if n.showHidden {
		s += "(show hidden) "
	}
	b := newBuffer(width)
	b.writes(util.TrimWcwidth(s, width), styleForMode)
	b.writes(" ", "")
	b.writes(n.filter, styleForFilter)
	b.dot = b.cursor()
	return b
}

func startNav(ed *Editor) {
	initNavigation(&ed.navigation)
	ed.mode = &ed.navigation
}

func navUp(ed *Editor) {
	ed.navigation.prev()
}

func navDown(ed *Editor) {
	ed.navigation.next()
}

func navPageUp(ed *Editor) {
	ed.navigation.current.pageUp()
	ed.navigation.refresh()
}

func navPageDown(ed *Editor) {
	ed.navigation.current.pageDown()
	ed.navigation.refresh()
}

func navLeft(ed *Editor) {
	ed.navigation.ascend()
}

func navRight(ed *Editor) {
	ed.navigation.descend()
}

func navTriggerShowHidden(ed *Editor) {
	ed.navigation.showHidden = !ed.navigation.showHidden
	ed.navigation.refresh()
}

func navTriggerFilter(ed *Editor) {
	ed.navigation.filtering = !ed.navigation.filtering
}

func navInsertSelected(ed *Editor) {
	ed.insertAtDot(parse.Quote(ed.navigation.current.selectedName()) + " ")
}

func navInsertSelectedAndQuit(ed *Editor) {
	ed.insertAtDot(parse.Quote(ed.navigation.current.selectedName()) + " ")
	ed.mode = &ed.insert
}

func navigationDefault(ed *Editor) {
	// Use key binding for insert mode without exiting nigation mode.
	k := ed.lastKey
	n := &ed.navigation
	if n.filtering && likeChar(k) {
		n.filter += k.String()
		n.refreshCurrent()
		n.refreshDirPreview()
	} else if n.filtering && k == (Key{Backspace, 0}) {
		_, size := utf8.DecodeLastRuneInString(n.filter)
		if size > 0 {
			n.filter = n.filter[:len(n.filter)-size]
			n.refreshCurrent()
			n.refreshDirPreview()
		}
	} else if f, ok := keyBindings[modeInsert][k]; ok {
		ed.CallFn(f)
	} else {
		ed.CallFn(keyBindings[modeInsert][Default])
	}
}

// Implementation.
// TODO(xiaq): Support file preview in navigation mode
// TODO(xiaq): Remember which file was selected in each directory.

var (
	errorEmptyCwd      = errors.New("current directory is empty")
	errorNoCwdInParent = errors.New("could not find current directory in ..")
)

func initNavigation(n *navigation) {
	*n = navigation{}
	n.refresh()
}

func (n *navigation) maintainSelected(name string) {
	n.current.selected = 0
	for i, s := range n.current.candidates {
		if s.text > name {
			break
		}
		n.current.selected = i
	}
}

func (n *navigation) refreshCurrent() {
	selectedName := n.current.selectedName()
	all, err := n.loaddir(".")
	if err != nil {
		n.current = newErrNavColumn(err)
		return
	}
	// Try to select the old selected file.
	// XXX(xiaq): This would break when we support alternative ordering.
	n.current = newNavColumn(all, func(i int) bool {
		return i == 0 || all[i].text <= selectedName
	})
	n.current.changeFilter(n.filter)
	n.maintainSelected(selectedName)
}

func (n *navigation) refreshParent() {
	wd, err := os.Getwd()
	if err != nil {
		n.parent = newErrNavColumn(err)
		return
	}
	if wd == "/" {
		n.parent = newNavColumn(nil, nil)
	} else {
		all, err := n.loaddir("..")
		if err != nil {
			n.parent = newErrNavColumn(err)
			return
		}
		cwd, err := os.Stat(".")
		if err != nil {
			n.parent = newErrNavColumn(err)
			return
		}
		n.parent = newNavColumn(all, func(i int) bool {
			d, _ := os.Lstat("../" + all[i].text)
			return os.SameFile(d, cwd)
		})
	}
}

func (n *navigation) refreshDirPreview() {
	if n.current.selected != -1 {
		name := n.current.selectedName()
		fi, err := os.Stat(name)
		if err != nil {
			n.dirPreview = newErrNavColumn(err)
			return
		}
		if fi.Mode().IsDir() {
			all, err := n.loaddir(name)
			if err != nil {
				n.dirPreview = newErrNavColumn(err)
				return
			}
			n.dirPreview = newNavColumn(all, func(int) bool { return false })
		} else {
			// TODO(xiaq): Support regular file preview in navigation mode
			n.dirPreview = nil
		}
	} else {
		n.dirPreview = nil
	}
}

// refresh rereads files in current and parent directories and maintains the
// selected file if possible.
func (n *navigation) refresh() {
	n.refreshCurrent()
	n.refreshParent()
	n.refreshDirPreview()
}

// ascend changes current directory to the parent.
// TODO(xiaq): navigation.{ascend descend} bypasses the cd builtin. This can be
// problematic if cd acquires more functionality (e.g. trigger a hook).
func (n *navigation) ascend() error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	if wd == "/" {
		return nil
	}

	name := n.parent.selectedName()
	err = os.Chdir("..")
	if err != nil {
		return err
	}
	n.filter = ""
	n.refresh()
	n.maintainSelected(name)
	// XXX Refresh dir preview again. We should perhaps not have used refresh
	// above.
	n.refreshDirPreview()
	return nil
}

// descend changes current directory to the selected file, if it is a
// directory.
func (n *navigation) descend() error {
	if n.current.selected == -1 {
		return errorEmptyCwd
	}
	name := n.current.selectedName()
	err := os.Chdir(name)
	if err != nil {
		return err
	}
	n.filter = ""
	n.current.selected = -1
	n.refresh()
	n.refreshDirPreview()
	return nil
}

// prev selects the previous file.
func (n *navigation) prev() {
	if n.current.selected > 0 {
		n.current.selected--
	}
	n.refresh()
}

// next selects the next file.
func (n *navigation) next() {
	if n.current.selected != -1 && n.current.selected < len(n.current.candidates)-1 {
		n.current.selected++
	}
	n.refresh()
}

func (n *navigation) loaddir(dir string) ([]styled, error) {
	f, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	infos, err := f.Readdir(0)
	if err != nil {
		return nil, err
	}
	var all []styled
	lsColor := getLsColor()
	for _, info := range infos {
		if n.showHidden || info.Name()[0] != '.' {
			name := info.Name()
			all = append(all, styled{name, lsColor.getStyle(path.Join(dir, name))})
		}
	}
	sortStyleds(all)

	return all, nil
}

const (
	navigationListingColMargin          = 1
	navigationListingMinWidthForPadding = 5

	parentColumnWeight  = 3.0
	currentColumnWeight = 8.0
	previewColumnWeight = 9.0
)

func (nav *navigation) List(width, maxHeight int) *buffer {
	margin := navigationListingColMargin

	w := width - margin*2
	ws := distributeWidths(w,
		[]float64{
			parentColumnWeight, currentColumnWeight, previewColumnWeight},
		[]int{
			nav.parent.FullWidth(maxHeight),
			nav.current.FullWidth(maxHeight),
			nav.dirPreview.FullWidth(maxHeight),
		})
	wParent, wCurrent, wPreview := ws[0], ws[1], ws[2]

	b := nav.parent.List(wParent, maxHeight)

	bCurrent := nav.current.List(wCurrent, maxHeight)
	b.extendHorizontal(bCurrent, wParent+margin)

	if wPreview > 0 {
		bPreview := nav.dirPreview.List(wPreview, maxHeight)
		b.extendHorizontal(bPreview, wParent+wCurrent+2*margin)
	}

	return b
}

// navColumn is a column in the navigation layout.
type navColumn struct {
	listing
	all        []styled
	candidates []styled
	// selected int
	err error
}

func newNavColumn(all []styled, sel func(int) bool) *navColumn {
	nc := &navColumn{all: all, candidates: all}
	nc.provider = nc
	nc.selected = -1
	for i := range all {
		if sel(i) {
			nc.selected = i
		}
	}
	return nc
}

func newErrNavColumn(err error) *navColumn {
	nc := &navColumn{err: err}
	nc.provider = nc
	return nc
}

func (nc *navColumn) Placeholder() string {
	if nc.err != nil {
		return nc.err.Error()
	}
	return ""
}

func (nc *navColumn) Len() int {
	return len(nc.candidates)
}

func (nc *navColumn) Show(i, w int) styled {
	s := nc.candidates[i]
	if w >= navigationListingMinWidthForPadding {
		return styled{" " + util.ForceWcwidth(s.text, w-2), s.style}
	}
	return styled{util.ForceWcwidth(s.text, w), s.style}
}

func (nc *navColumn) Filter(filter string) int {
	nc.candidates = nc.candidates[:0]
	for _, s := range nc.all {
		if strings.Contains(s.text, filter) {
			nc.candidates = append(nc.candidates, s)
		}
	}
	return 0
}

func (nc *navColumn) FullWidth(h int) int {
	if nc == nil {
		return 0
	}
	maxw := 0
	for _, s := range nc.candidates {
		maxw = max(maxw, util.Wcswidth(s.text))
	}
	if maxw >= navigationListingMinWidthForPadding {
		maxw += 2
	}
	if len(nc.candidates) > h {
		maxw++
	}
	return maxw
}

func (nc *navColumn) Accept(i int, ed *Editor) {
	// TODO
}

func (nc *navColumn) ModeTitle(i int) string {
	// Not used
	return ""
}

func (nc *navColumn) selectedName() string {
	if nc == nil || nc.selected == -1 || nc.selected >= len(nc.candidates) {
		return ""
	}
	return nc.candidates[nc.selected].text
}
<commit_msg>Support file preview in nav mode.<commit_after>package edit

import (
	"errors"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"unicode/utf8"

	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/util"
)

// Navigation subsystem.

// Interface.

type navigation struct {
	current    *navColumn
	parent     *navColumn
	preview    *navColumn
	showHidden bool
	filtering  bool
	filter     string
}

func (*navigation) Mode() ModeType {
	return modeNavigation
}

func (n *navigation) ModeLine(width int) *buffer {
	s := " NAVIGATING "
	if n.showHidden {
		s += "(show hidden) "
	}
	b := newBuffer(width)
	b.writes(util.TrimWcwidth(s, width), styleForMode)
	b.writes(" ", "")
	b.writes(n.filter, styleForFilter)
	b.dot = b.cursor()
	return b
}

func startNav(ed *Editor) {
	initNavigation(&ed.navigation)
	ed.mode = &ed.navigation
}

func navUp(ed *Editor) {
	ed.navigation.prev()
}

func navDown(ed *Editor) {
	ed.navigation.next()
}

func navPageUp(ed *Editor) {
	ed.navigation.current.pageUp()
	ed.navigation.refresh()
}

func navPageDown(ed *Editor) {
	ed.navigation.current.pageDown()
	ed.navigation.refresh()
}

func navLeft(ed *Editor) {
	ed.navigation.ascend()
}

func navRight(ed *Editor) {
	ed.navigation.descend()
}

func navTriggerShowHidden(ed *Editor) {
	ed.navigation.showHidden = !ed.navigation.showHidden
	ed.navigation.refresh()
}

func navTriggerFilter(ed *Editor) {
	ed.navigation.filtering = !ed.navigation.filtering
}

func navInsertSelected(ed *Editor) {
	ed.insertAtDot(parse.Quote(ed.navigation.current.selectedName()) + " ")
}

func navInsertSelectedAndQuit(ed *Editor) {
	ed.insertAtDot(parse.Quote(ed.navigation.current.selectedName()) + " ")
	ed.mode = &ed.insert
}

func navigationDefault(ed *Editor) {
	// Use key binding for insert mode without exiting nigation mode.
	k := ed.lastKey
	n := &ed.navigation
	if n.filtering && likeChar(k) {
		n.filter += k.String()
		n.refreshCurrent()
		n.refreshDirPreview()
	} else if n.filtering && k == (Key{Backspace, 0}) {
		_, size := utf8.DecodeLastRuneInString(n.filter)
		if size > 0 {
			n.filter = n.filter[:len(n.filter)-size]
			n.refreshCurrent()
			n.refreshDirPreview()
		}
	} else if f, ok := keyBindings[modeInsert][k]; ok {
		ed.CallFn(f)
	} else {
		ed.CallFn(keyBindings[modeInsert][Default])
	}
}

// Implementation.
// TODO(xiaq): Support file preview in navigation mode
// TODO(xiaq): Remember which file was selected in each directory.

var (
	errorEmptyCwd      = errors.New("current directory is empty")
	errorNoCwdInParent = errors.New("could not find current directory in ..")
)

func initNavigation(n *navigation) {
	*n = navigation{}
	n.refresh()
}

func (n *navigation) maintainSelected(name string) {
	n.current.selected = 0
	for i, s := range n.current.candidates {
		if s.text > name {
			break
		}
		n.current.selected = i
	}
}

func (n *navigation) refreshCurrent() {
	selectedName := n.current.selectedName()
	all, err := n.loaddir(".")
	if err != nil {
		n.current = newErrNavColumn(err)
		return
	}
	// Try to select the old selected file.
	// XXX(xiaq): This would break when we support alternative ordering.
	n.current = newNavColumn(all, func(i int) bool {
		return i == 0 || all[i].text <= selectedName
	})
	n.current.changeFilter(n.filter)
	n.maintainSelected(selectedName)
}

func (n *navigation) refreshParent() {
	wd, err := os.Getwd()
	if err != nil {
		n.parent = newErrNavColumn(err)
		return
	}
	if wd == "/" {
		n.parent = newNavColumn(nil, nil)
	} else {
		all, err := n.loaddir("..")
		if err != nil {
			n.parent = newErrNavColumn(err)
			return
		}
		cwd, err := os.Stat(".")
		if err != nil {
			n.parent = newErrNavColumn(err)
			return
		}
		n.parent = newNavColumn(all, func(i int) bool {
			d, _ := os.Lstat("../" + all[i].text)
			return os.SameFile(d, cwd)
		})
	}
}

func (n *navigation) refreshDirPreview() {
	if n.current.selected != -1 {
		name := n.current.selectedName()
		fi, err := os.Stat(name)
		if err != nil {
			n.preview = newErrNavColumn(err)
			return
		}
		if fi.Mode().IsDir() {
			all, err := n.loaddir(name)
			if err != nil {
				n.preview = newErrNavColumn(err)
				return
			}
			n.preview = newNavColumn(all, func(int) bool { return false })
		} else {
			n.preview = newFilePreviewNavColumn(name)
		}
	} else {
		n.preview = nil
	}
}

// refresh rereads files in current and parent directories and maintains the
// selected file if possible.
func (n *navigation) refresh() {
	n.refreshCurrent()
	n.refreshParent()
	n.refreshDirPreview()
}

// ascend changes current directory to the parent.
// TODO(xiaq): navigation.{ascend descend} bypasses the cd builtin. This can be
// problematic if cd acquires more functionality (e.g. trigger a hook).
func (n *navigation) ascend() error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	if wd == "/" {
		return nil
	}

	name := n.parent.selectedName()
	err = os.Chdir("..")
	if err != nil {
		return err
	}
	n.filter = ""
	n.refresh()
	n.maintainSelected(name)
	// XXX Refresh dir preview again. We should perhaps not have used refresh
	// above.
	n.refreshDirPreview()
	return nil
}

// descend changes current directory to the selected file, if it is a
// directory.
func (n *navigation) descend() error {
	if n.current.selected == -1 {
		return errorEmptyCwd
	}
	name := n.current.selectedName()
	err := os.Chdir(name)
	if err != nil {
		return err
	}
	n.filter = ""
	n.current.selected = -1
	n.refresh()
	n.refreshDirPreview()
	return nil
}

// prev selects the previous file.
func (n *navigation) prev() {
	if n.current.selected > 0 {
		n.current.selected--
	}
	n.refresh()
}

// next selects the next file.
func (n *navigation) next() {
	if n.current.selected != -1 && n.current.selected < len(n.current.candidates)-1 {
		n.current.selected++
	}
	n.refresh()
}

func (n *navigation) loaddir(dir string) ([]styled, error) {
	f, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	infos, err := f.Readdir(0)
	if err != nil {
		return nil, err
	}
	var all []styled
	lsColor := getLsColor()
	for _, info := range infos {
		if n.showHidden || info.Name()[0] != '.' {
			name := info.Name()
			all = append(all, styled{name, lsColor.getStyle(path.Join(dir, name))})
		}
	}
	sortStyleds(all)

	return all, nil
}

const (
	navigationListingColMargin          = 1
	navigationListingMinWidthForPadding = 5

	parentColumnWeight  = 3.0
	currentColumnWeight = 8.0
	previewColumnWeight = 9.0
)

func (nav *navigation) List(width, maxHeight int) *buffer {
	margin := navigationListingColMargin

	w := width - margin*2
	ws := distributeWidths(w,
		[]float64{
			parentColumnWeight, currentColumnWeight, previewColumnWeight},
		[]int{
			nav.parent.FullWidth(maxHeight),
			nav.current.FullWidth(maxHeight),
			nav.preview.FullWidth(maxHeight),
		})
	wParent, wCurrent, wPreview := ws[0], ws[1], ws[2]

	b := nav.parent.List(wParent, maxHeight)

	bCurrent := nav.current.List(wCurrent, maxHeight)
	b.extendHorizontal(bCurrent, wParent+margin)

	if wPreview > 0 {
		bPreview := nav.preview.List(wPreview, maxHeight)
		b.extendHorizontal(bPreview, wParent+wCurrent+2*margin)
	}

	return b
}

// navColumn is a column in the navigation layout.
type navColumn struct {
	listing
	all        []styled
	candidates []styled
	// selected int
	err error
}

func newNavColumn(all []styled, sel func(int) bool) *navColumn {
	nc := &navColumn{all: all, candidates: all}
	nc.provider = nc
	nc.selected = -1
	for i := range all {
		if sel(i) {
			nc.selected = i
		}
	}
	return nc
}

func newErrNavColumn(err error) *navColumn {
	nc := &navColumn{err: err}
	nc.provider = nc
	return nc
}

var ErrNotValidUTF8 = errors.New("not utf8")

func newFilePreviewNavColumn(fname string) *navColumn {
	// XXX This implementation is a bit hacky, since listing is not really
	// intended for listing file content. Among other shortcomings, it always
	// reads the entire file.
	var err error
	file, err := os.Open(fname)
	if err != nil {
		return newErrNavColumn(err)
	}
	bs, err := ioutil.ReadAll(file)
	if err != nil {
		return newErrNavColumn(err)
	}
	content := string(bs)
	if !utf8.ValidString(content) {
		return newErrNavColumn(ErrNotValidUTF8)
	}
	lines := strings.Split(content, "\n")
	styleds := make([]styled, len(lines))
	for i, line := range lines {
		styleds[i] = styled{line, ""}
	}
	return newNavColumn(styleds, func(int) bool { return false })
}

func (nc *navColumn) Placeholder() string {
	if nc.err != nil {
		return nc.err.Error()
	}
	return ""
}

func (nc *navColumn) Len() int {
	return len(nc.candidates)
}

func (nc *navColumn) Show(i, w int) styled {
	s := nc.candidates[i]
	if w >= navigationListingMinWidthForPadding {
		return styled{" " + util.ForceWcwidth(s.text, w-2), s.style}
	}
	return styled{util.ForceWcwidth(s.text, w), s.style}
}

func (nc *navColumn) Filter(filter string) int {
	nc.candidates = nc.candidates[:0]
	for _, s := range nc.all {
		if strings.Contains(s.text, filter) {
			nc.candidates = append(nc.candidates, s)
		}
	}
	return 0
}

func (nc *navColumn) FullWidth(h int) int {
	if nc == nil {
		return 0
	}
	maxw := 0
	for _, s := range nc.candidates {
		maxw = max(maxw, util.Wcswidth(s.text))
	}
	if maxw >= navigationListingMinWidthForPadding {
		maxw += 2
	}
	if len(nc.candidates) > h {
		maxw++
	}
	return maxw
}

func (nc *navColumn) Accept(i int, ed *Editor) {
	// TODO
}

func (nc *navColumn) ModeTitle(i int) string {
	// Not used
	return ""
}

func (nc *navColumn) selectedName() string {
	if nc == nil || nc.selected == -1 || nc.selected >= len(nc.candidates) {
		return ""
	}
	return nc.candidates[nc.selected].text
}
<|endoftext|>


<commit_before>// Copyright 2020 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
	"encoding/json"
	"fmt"
	"net/http"

	. "github.com/pingcap/check"
	"github.com/pingcap/pd/v4/server"
	"github.com/pingcap/pd/v4/server/schedule/placement"
)

var _ = Suite(&testRuleSuite{})

type testRuleSuite struct {
	svr       *server.Server
	cleanup   cleanUpFunc
	urlPrefix string
}

func (s *testRuleSuite) SetUpSuite(c *C) {
	s.svr, s.cleanup = mustNewServer(c)
	mustWaitLeader(c, []*server.Server{s.svr})

	addr := s.svr.GetAddr()
	s.urlPrefix = fmt.Sprintf("%s%s/api/v1/config", addr, apiPrefix)

	mustBootstrapCluster(c, s.svr)
}

func (s *testRuleSuite) TearDownSuite(c *C) {
	s.cleanup()
}

func (s *testRuleSuite) Testrule(c *C) {
	PDServerCfg := s.svr.GetConfig().PDServerCfg
	PDServerCfg.KeyType = "raw"
	err := s.svr.SetPDServerConfig(PDServerCfg)
	c.Assert(err, IsNil)
	c.Assert(postJSON(testDialClient, s.urlPrefix, []byte(`{"enable-placement-rules":"true"}`)), IsNil)
	rule1 := placement.Rule{GroupID: "a", ID: "10", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: 1}
	rule2 := placement.Rule{GroupID: "a", ID: "20", StartKeyHex: "3333", EndKeyHex: "5555", Role: "voter", Count: 2}
	rule3 := placement.Rule{GroupID: "b", ID: "20", StartKeyHex: "5555", EndKeyHex: "7777", Role: "voter", Count: 3}

	//Set
	postData, err := json.Marshal(rule1)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", postData)
	c.Assert(err, IsNil)
	postData, err = json.Marshal(rule2)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", postData)
	c.Assert(err, IsNil)
	postData, err = json.Marshal(rule3)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", postData)
	c.Assert(err, IsNil)

	//Get
	var resp placement.Rule
	err = readJSON(testDialClient, s.urlPrefix+"/rule/a/10", &resp)
	c.Assert(err, IsNil)
	compareRule(c, &resp, &rule1)

	//GetAll
	var resp2 []*placement.Rule
	err = readJSON(testDialClient, s.urlPrefix+"/rules", &resp2)
	c.Assert(err, IsNil)
	c.Assert(len(resp2), Equals, 4)

	//GetAllByGroup
	err = readJSON(testDialClient, s.urlPrefix+"/rules/group/b", &resp2)
	c.Assert(err, IsNil)
	c.Assert(len(resp2), Equals, 1)
	compareRule(c, resp2[0], &rule3)

	//GetAllByRegion
	r := newTestRegionInfo(4, 1, []byte([]byte{0x22, 0x22}), []byte{0x33, 0x33})
	mustRegionHeartbeat(c, s.svr, r)
	err = readJSON(testDialClient, s.urlPrefix+"/rules/region/4", &resp2)
	c.Assert(err, IsNil)
	c.Assert(len(resp2), Equals, 2)
	if resp2[0].GroupID == "pd" {
		compareRule(c, resp2[1], &rule1)
	} else {
		compareRule(c, resp2[0], &rule1)
	}

	//GetAllByKey
	err = readJSON(testDialClient, s.urlPrefix+"/rules/key/4444", &resp2)
	c.Assert(err, IsNil)
	c.Assert(len(resp2), Equals, 2)
	if resp2[0].GroupID == "pd" {
		compareRule(c, resp2[1], &rule2)
	} else {
		compareRule(c, resp2[0], &rule2)
	}

	//Delete
	resp3, err := doDelete(testDialClient, s.urlPrefix+"/rule/a/10")
	c.Assert(err, IsNil)
	c.Assert(resp3.StatusCode, Equals, http.StatusOK)
}

func compareRule(c *C, r1 *placement.Rule, r2 *placement.Rule) {
	c.Assert(r1.GroupID, Equals, r2.GroupID)
	c.Assert(r1.ID, Equals, r2.ID)
	c.Assert(r1.StartKeyHex, Equals, r2.StartKeyHex)
	c.Assert(r1.EndKeyHex, Equals, r2.EndKeyHex)
	c.Assert(r1.Role, Equals, r2.Role)
	c.Assert(r1.Count, Equals, r2.Count)
}
<commit_msg>test: improve coverage for rule.go  (#2589)<commit_after>// Copyright 2020 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	. "github.com/pingcap/check"
	"github.com/pingcap/pd/v4/server"
	"github.com/pingcap/pd/v4/server/schedule/placement"
)

var _ = Suite(&testRuleSuite{})

type testRuleSuite struct {
	svr       *server.Server
	cleanup   cleanUpFunc
	urlPrefix string
}

func (s *testRuleSuite) SetUpSuite(c *C) {
	s.svr, s.cleanup = mustNewServer(c)
	mustWaitLeader(c, []*server.Server{s.svr})

	addr := s.svr.GetAddr()
	s.urlPrefix = fmt.Sprintf("%s%s/api/v1/config", addr, apiPrefix)

	mustBootstrapCluster(c, s.svr)
	PDServerCfg := s.svr.GetConfig().PDServerCfg
	PDServerCfg.KeyType = "raw"
	err := s.svr.SetPDServerConfig(PDServerCfg)
	c.Assert(err, IsNil)
	c.Assert(postJSON(testDialClient, s.urlPrefix, []byte(`{"enable-placement-rules":"true"}`)), IsNil)
}

func (s *testRuleSuite) TearDownSuite(c *C) {
	s.cleanup()
}

func (s *testRuleSuite) TestSet(c *C) {
	rule := placement.Rule{GroupID: "a", ID: "10", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: 1}
	successData, err := json.Marshal(rule)
	c.Assert(err, IsNil)
	parseErrData := []byte("foo")
	rule1 := placement.Rule{GroupID: "a", ID: "10", StartKeyHex: "XXXX", EndKeyHex: "3333", Role: "voter", Count: 1}
	checkErrData, err := json.Marshal(rule1)
	c.Assert(err, IsNil)
	rule2 := placement.Rule{GroupID: "a", ID: "10", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: -1}
	setErrData, err := json.Marshal(rule2)
	c.Assert(err, IsNil)

	testcases := []struct {
		name     string
		rawData  []byte
		success  bool
		response string
	}{
		{
			name:     "Set rule success",
			rawData:  successData,
			success:  true,
			response: "",
		},
		{
			name:    "Parse Json failed",
			rawData: parseErrData,
			success: false,
			response: `{
  "code": "input",
  "msg": "invalid character 'o' in literal false (expecting 'a')",
  "data": {
    "Offset": 2
  }
}
`,
		},
		{
			name:    "Check rule failed",
			rawData: checkErrData,
			success: false,
			response: `"start key is not in hex format: encoding/hex: invalid byte: U+0058 'X'"
`,
		},
		{
			name:    "Set Rule Failed",
			rawData: setErrData,
			success: false,
			response: `"invalid count -1"
`,
		},
	}

	for _, testcase := range testcases {
		c.Log(testcase.name)
		err = postJSON(testDialClient, s.urlPrefix+"/rule", testcase.rawData)
		if testcase.success {
			c.Assert(err, IsNil)
		} else {
			c.Assert(err, NotNil)
			c.Assert(err.Error(), Equals, testcase.response)
		}
	}
}

func (s *testRuleSuite) TestGet(c *C) {
	rule := placement.Rule{GroupID: "a", ID: "20", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: 1}
	data, err := json.Marshal(rule)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", data)
	c.Assert(err, IsNil)

	testcases := []struct {
		name  string
		rule  placement.Rule
		found bool
		code  string
	}{
		{
			name:  "found",
			rule:  rule,
			found: true,
			code:  "",
		},
		{
			name:  "not found",
			rule:  placement.Rule{GroupID: "a", ID: "30", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: 1},
			found: false,
			code:  "404",
		},
	}
	for _, testcase := range testcases {
		c.Log(testcase.name)
		var resp placement.Rule
		url := fmt.Sprintf("%s/rule/%s/%s", s.urlPrefix, testcase.rule.GroupID, testcase.rule.ID)
		err = readJSON(testDialClient, url, &resp)
		if testcase.found {
			c.Assert(err, IsNil)
			compareRule(c, &resp, &testcase.rule)
		} else {
			c.Assert(err, NotNil)
			c.Assert(strings.HasSuffix(err.Error(), testcase.code), Equals, true)
		}
	}
}

func (s *testRuleSuite) TestGetAll(c *C) {
	rule := placement.Rule{GroupID: "b", ID: "20", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: 1}
	data, err := json.Marshal(rule)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", data)
	c.Assert(err, IsNil)

	var resp2 []*placement.Rule
	err = readJSON(testDialClient, s.urlPrefix+"/rules", &resp2)
	c.Assert(err, IsNil)
	c.Assert(len(resp2), GreaterEqual, 1)
}

func (s *testRuleSuite) TestGetAllByGroup(c *C) {
	rule := placement.Rule{GroupID: "c", ID: "20", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: 1}
	data, err := json.Marshal(rule)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", data)
	c.Assert(err, IsNil)

	rule1 := placement.Rule{GroupID: "c", ID: "30", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: 1}
	data, err = json.Marshal(rule1)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", data)
	c.Assert(err, IsNil)

	testcases := []struct {
		name    string
		groupID string
		count   int
	}{
		{
			name:    "found group c",
			groupID: "c",
			count:   2,
		},
		{
			name:    "not found d",
			groupID: "d",
			count:   0,
		},
	}

	for _, testcase := range testcases {
		c.Log(testcase.name)
		var resp []*placement.Rule
		url := fmt.Sprintf("%s/rules/group/%s", s.urlPrefix, testcase.groupID)
		err = readJSON(testDialClient, url, &resp)
		c.Assert(err, IsNil)
		c.Assert(len(resp), Equals, testcase.count)
		if testcase.count == 2 {
			compareRule(c, resp[0], &rule)
			compareRule(c, resp[1], &rule1)
		}
	}
}

func (s *testRuleSuite) TestGetAllByRegion(c *C) {
	rule := placement.Rule{GroupID: "e", ID: "20", StartKeyHex: "1111", EndKeyHex: "3333", Role: "voter", Count: 1}
	data, err := json.Marshal(rule)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", data)
	c.Assert(err, IsNil)

	r := newTestRegionInfo(4, 1, []byte([]byte{0x22, 0x22}), []byte{0x33, 0x33})
	mustRegionHeartbeat(c, s.svr, r)

	testcases := []struct {
		name     string
		regionID string
		success  bool
		code     string
	}{
		{
			name:     "found region",
			regionID: "4",
			success:  true,
		},
		{
			name:     "parse regionId failed",
			regionID: "abc",
			success:  false,
			code:     "400",
		},
		{
			name:     "region not found",
			regionID: "5",
			success:  false,
			code:     "404",
		},
	}
	for _, testcase := range testcases {
		c.Log(testcase.name)
		var resp []*placement.Rule
		url := fmt.Sprintf("%s/rules/region/%s", s.urlPrefix, testcase.regionID)
		err = readJSON(testDialClient, url, &resp)
		if testcase.success {
			c.Assert(err, IsNil)
			for _, r := range resp {
				if r.GroupID == "e" {
					compareRule(c, r, &rule)
				}
			}
		} else {
			c.Assert(err, NotNil)
			c.Assert(strings.HasSuffix(err.Error(), testcase.code), Equals, true)
		}
	}
}

func (s *testRuleSuite) TestGetAllByKey(c *C) {
	rule := placement.Rule{GroupID: "f", ID: "40", StartKeyHex: "8888", EndKeyHex: "9111", Role: "voter", Count: 1}
	data, err := json.Marshal(rule)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", data)
	c.Assert(err, IsNil)

	testcases := []struct {
		name     string
		key      string
		success  bool
		respSize int
		code     string
	}{
		{
			name:     "key in range",
			key:      "8899",
			success:  true,
			respSize: 2,
		},
		{
			name:     "parse key failed",
			key:      "abc",
			success:  false,
			code:     "400",
			respSize: 0,
		},
		{
			name:     "key out of range",
			key:      "9999",
			success:  true,
			respSize: 1,
		},
	}

	for _, testcase := range testcases {
		c.Log(testcase.name)
		var resp []*placement.Rule
		url := fmt.Sprintf("%s/rules/key/%s", s.urlPrefix, testcase.key)
		err = readJSON(testDialClient, url, &resp)
		if testcase.success {
			c.Assert(err, IsNil)
			c.Assert(len(resp), Equals, testcase.respSize)
		} else {
			c.Assert(err, NotNil)
			c.Assert(strings.HasSuffix(err.Error(), testcase.code), Equals, true)
		}
	}
}

func (s *testRuleSuite) TestDelete(c *C) {
	rule := placement.Rule{GroupID: "g", ID: "10", StartKeyHex: "8888", EndKeyHex: "9111", Role: "voter", Count: 1}
	data, err := json.Marshal(rule)
	c.Assert(err, IsNil)
	err = postJSON(testDialClient, s.urlPrefix+"/rule", data)
	c.Assert(err, IsNil)

	testcases := []struct {
		name    string
		groupID string
		id      string
	}{
		{
			name:    "delete existed rule",
			groupID: "g",
			id:      "10",
		},
		{
			name:    "delete non-existed rule",
			groupID: "g",
			id:      "15",
		},
	}
	for _, testcase := range testcases {
		c.Log(testcase.name)
		url := fmt.Sprintf("%s/rule/%s/%s", s.urlPrefix, testcase.groupID, testcase.id)
		resp, err := doDelete(testDialClient, url)
		c.Assert(err, IsNil)
		c.Assert(resp.StatusCode, Equals, http.StatusOK)
	}
}

func compareRule(c *C, r1 *placement.Rule, r2 *placement.Rule) {
	c.Assert(r1.GroupID, Equals, r2.GroupID)
	c.Assert(r1.ID, Equals, r2.ID)
	c.Assert(r1.StartKeyHex, Equals, r2.StartKeyHex)
	c.Assert(r1.EndKeyHex, Equals, r2.EndKeyHex)
	c.Assert(r1.Role, Equals, r2.Role)
	c.Assert(r1.Count, Equals, r2.Count)
}
<|endoftext|>


<commit_before>package builtin

import (
	"errors"
	"github.com/mediocregopher/hyrax/server/auth"
	"github.com/mediocregopher/hyrax/server/dist"
	"github.com/mediocregopher/hyrax/server/storage-router"
	"github.com/mediocregopher/hyrax/types"
	stypes "github.com/mediocregopher/hyrax/server/types"
)

// TODO make success condition not be nil

var wrongNumArgs = errors.New("wrong number of arguments")
var wrongArgType = errors.New("wrong argument type")

func argsToAddr(cmd *types.ClientCommand) (*string, error) {
	if len(cmd.Args) != 1 {
		return nil, wrongNumArgs
	} else if addr, ok := cmd.Args[0].(string); ok {
		return &addr, nil
	} else {
		return nil, wrongArgType
	}
}

// ANodeAdd uses the first argument as a node address, and adds that address to
// the hyrax mesh this one is in.
func ANodeAdd(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	node, err := argsToAddr(cmd)
	if err != nil {
		return nil, err
	}

	return nil, dist.AddNode(node)
}

// ANodeRem uses the first argument as a node address, and removes that node
// from the hyrax mesh, if it was in there to begin with
func ANodeRem(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	node, err := argsToAddr(cmd)
	if err != nil {
		return nil, err
	}

	dist.RemNode(node)
	return nil, nil
}

// TODO command to list nodes

// ABucketSet sets the storage bucket at the index given as the first argument.
// The second and third arguments are the connection type and address, and the
// rest are extra that are passed through depending on the storage type
func ABucketSet(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	if len(cmd.Args) >= 3 {
		return nil, wrongNumArgs
	}

	var ok bool
	var bucket int
	var conntype, addr string
	var extra []interface{}

	if bucket, ok = cmd.Args[0].(int); !ok {
		return nil, wrongArgType
	} else if conntype, ok = cmd.Args[1].(string); !ok {
		return nil, wrongArgType
	} else if addr, ok = cmd.Args[2].(string); !ok {
		return nil, wrongArgType
	}

	extra = cmd.Args[3:]
	return nil, router.SetBucket(bucket, conntype, addr, extra...)
}

// ABuckets returns the current bucket list
func ABuckets(_ stypes.ClientId, _ *types.ClientCommand) (interface{}, error) {
	return router.GetBuckets, nil
}

func argsToByteSlice(cmd *types.ClientCommand) ([]byte, error) {
	if len(cmd.Args) != 1 {
		return nil, wrongNumArgs
	} else if s, ok := cmd.Args[0].(string); ok {
		return []byte(s), nil
	} else {
		return nil, wrongArgType
	}
}

// AGlobalSecretAdd adds a global secret to every node in the mesh's global
// secret list
func AGlobalSecretAdd(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	secret, err := argsToByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	auth.AddGlobalSecret(secret)
	return nil, nil
}

// AGlobalSecretRem removes a global secret from every nod in the mesh's global
// secret list
func AGlobalSecretRem(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	secret, err := argsToByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	auth.RemGlobalSecret(secret)
	return nil, nil
}

// AGlobalSecrets returns the list of currently active secrets on this node (and
// presumably every other node)
func AGlobalSecrets(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	secretsB := auth.GetGlobalSecrets()
	secrets := make([]string, len(secretsB))
	for i := range secretsB {
		secrets[i] = string(secretsB[i])
	}
	return secrets, nil
}

func argsToByterAndByteSlice(
	cmd *types.ClientCommand) (types.Byter, []byte, error) {
	var b1, b2 []byte
	if len(cmd.Args) != 2 {
		return nil, nil, wrongNumArgs
	} else if b1s, ok := cmd.Args[0].(string); ok {
		b1 = []byte(b1s)
	} else if b2s, ok := cmd.Args[1].(string); ok {
		b2 = []byte(b2s)
	} else {
		return nil, nil, wrongArgType
	}

	return types.NewByter(b1), b2, nil
}

// ASecretAdd adds a particular secret to an individual key
func ASecretAdd(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	key, secret, err := argsToByterAndByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	return nil, auth.AddSecret(key, secret)
}

// ASecretRem removes a particular secret from an individual key
func ASecretRem(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	key, secret, err := argsToByterAndByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	return nil, auth.RemSecret(key, secret)
}

// ASecrets returns all the particular secrets for an individual key
func ASecrets(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	keyB, err := argsToByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	secretsB, err := auth.GetSecrets(types.NewByter(keyB))
	if err != nil {
		return nil, err
	}

	secrets := make([]string, len(secretsB))
	for i := range secretsB {
		secrets[i] = string(secretsB[i])
	}

	return secrets, nil
}
<commit_msg>fixed typo in AGlobalSecretRem docstring<commit_after>package builtin

import (
	"errors"
	"github.com/mediocregopher/hyrax/server/auth"
	"github.com/mediocregopher/hyrax/server/dist"
	"github.com/mediocregopher/hyrax/server/storage-router"
	"github.com/mediocregopher/hyrax/types"
	stypes "github.com/mediocregopher/hyrax/server/types"
)

// TODO make success condition not be nil

var wrongNumArgs = errors.New("wrong number of arguments")
var wrongArgType = errors.New("wrong argument type")

func argsToAddr(cmd *types.ClientCommand) (*string, error) {
	if len(cmd.Args) != 1 {
		return nil, wrongNumArgs
	} else if addr, ok := cmd.Args[0].(string); ok {
		return &addr, nil
	} else {
		return nil, wrongArgType
	}
}

// ANodeAdd uses the first argument as a node address, and adds that address to
// the hyrax mesh this one is in.
func ANodeAdd(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	node, err := argsToAddr(cmd)
	if err != nil {
		return nil, err
	}

	return nil, dist.AddNode(node)
}

// ANodeRem uses the first argument as a node address, and removes that node
// from the hyrax mesh, if it was in there to begin with
func ANodeRem(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	node, err := argsToAddr(cmd)
	if err != nil {
		return nil, err
	}

	dist.RemNode(node)
	return nil, nil
}

// TODO command to list nodes

// ABucketSet sets the storage bucket at the index given as the first argument.
// The second and third arguments are the connection type and address, and the
// rest are extra that are passed through depending on the storage type
func ABucketSet(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	if len(cmd.Args) >= 3 {
		return nil, wrongNumArgs
	}

	var ok bool
	var bucket int
	var conntype, addr string
	var extra []interface{}

	if bucket, ok = cmd.Args[0].(int); !ok {
		return nil, wrongArgType
	} else if conntype, ok = cmd.Args[1].(string); !ok {
		return nil, wrongArgType
	} else if addr, ok = cmd.Args[2].(string); !ok {
		return nil, wrongArgType
	}

	extra = cmd.Args[3:]
	return nil, router.SetBucket(bucket, conntype, addr, extra...)
}

// ABuckets returns the current bucket list
func ABuckets(_ stypes.ClientId, _ *types.ClientCommand) (interface{}, error) {
	return router.GetBuckets, nil
}

func argsToByteSlice(cmd *types.ClientCommand) ([]byte, error) {
	if len(cmd.Args) != 1 {
		return nil, wrongNumArgs
	} else if s, ok := cmd.Args[0].(string); ok {
		return []byte(s), nil
	} else {
		return nil, wrongArgType
	}
}

// AGlobalSecretAdd adds a global secret to every node in the mesh's global
// secret list
func AGlobalSecretAdd(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	secret, err := argsToByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	auth.AddGlobalSecret(secret)
	return nil, nil
}

// AGlobalSecretRem removes a global secret from every node in the mesh's global
// secret list
func AGlobalSecretRem(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	secret, err := argsToByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	auth.RemGlobalSecret(secret)
	return nil, nil
}

// AGlobalSecrets returns the list of currently active secrets on this node (and
// presumably every other node)
func AGlobalSecrets(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	secretsB := auth.GetGlobalSecrets()
	secrets := make([]string, len(secretsB))
	for i := range secretsB {
		secrets[i] = string(secretsB[i])
	}
	return secrets, nil
}

func argsToByterAndByteSlice(
	cmd *types.ClientCommand) (types.Byter, []byte, error) {
	var b1, b2 []byte
	if len(cmd.Args) != 2 {
		return nil, nil, wrongNumArgs
	} else if b1s, ok := cmd.Args[0].(string); ok {
		b1 = []byte(b1s)
	} else if b2s, ok := cmd.Args[1].(string); ok {
		b2 = []byte(b2s)
	} else {
		return nil, nil, wrongArgType
	}

	return types.NewByter(b1), b2, nil
}

// ASecretAdd adds a particular secret to an individual key
func ASecretAdd(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	key, secret, err := argsToByterAndByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	return nil, auth.AddSecret(key, secret)
}

// ASecretRem removes a particular secret from an individual key
func ASecretRem(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	key, secret, err := argsToByterAndByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	return nil, auth.RemSecret(key, secret)
}

// ASecrets returns all the particular secrets for an individual key
func ASecrets(
	_ stypes.ClientId,
	cmd *types.ClientCommand) (interface{}, error) {
	keyB, err := argsToByteSlice(cmd)
	if err != nil {
		return nil, err
	}

	secretsB, err := auth.GetSecrets(types.NewByter(keyB))
	if err != nil {
		return nil, err
	}

	secrets := make([]string, len(secretsB))
	for i := range secretsB {
		secrets[i] = string(secretsB[i])
	}

	return secrets, nil
}
<|endoftext|>