<commit_before>package main

import (
	"io/ioutil"
	"os"
	"reflect"

	"launchpad.net/gnuflag"
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/cmd"
	"launchpad.net/juju-core/environs/dummy"
	"launchpad.net/juju-core/juju/testing"
)

type CmdSuite struct {
	testing.JujuConnSuite
	home fakeHome
}

var _ = Suite(&CmdSuite{})

var envConfig = `
default:
    peckham
environments:
    peckham:
        type: dummy
        state-server: false
        admin-secret: arble
        authorized-keys: i-am-a-key
    walthamstow:
        type: dummy
        state-server: false
        authorized-keys: i-am-a-key
    brokenenv:
        type: dummy
        broken: Bootstrap Destroy
        state-server: false
        authorized-keys: i-am-a-key
`

func (s *CmdSuite) SetUpTest(c *C) {
	s.JujuConnSuite.SetUpTest(c)
	s.home = makeFakeHome(c, "peckham", "walthamstow", "brokenenv")
	err := ioutil.WriteFile(homePath(".juju", "environments.yaml"), []byte(envConfig), 0666)
	c.Assert(err, IsNil)
}

func (s *CmdSuite) TearDownTest(c *C) {
	s.home.restore()
	s.JujuConnSuite.TearDownTest(c)
}

func newFlagSet() *gnuflag.FlagSet {
	return gnuflag.NewFlagSet("", gnuflag.ContinueOnError)
}

// testInit checks that a command initialises correctly
// with the given set of arguments.
func testInit(c *C, com cmd.Command, args []string, errPat string) {
	err := com.Init(newFlagSet(), args)
	if errPat != "" {
		c.Assert(err, ErrorMatches, errPat)
	} else {
		c.Assert(err, IsNil)
	}
}

// assertConnName asserts that the Command is using
// the given environment name.
// Since every command has a different type,
// we use reflection to look at the value of the
// Conn field in the value.
func assertConnName(c *C, com cmd.Command, name string) {
	v := reflect.ValueOf(com).Elem().FieldByName("EnvName")
	c.Assert(v.IsValid(), Equals, true)
	c.Assert(v.Interface(), Equals, name)
}

// All members of EnvironmentInitTests are tested for the -environment and -e
// flags, and that extra arguments will cause parsing to fail.
var EnvironmentInitTests = []func() (cmd.Command, []string){
	func() (cmd.Command, []string) { return new(BootstrapCommand), nil },
	func() (cmd.Command, []string) { return new(DestroyEnvironmentCommand), nil },
	func() (cmd.Command, []string) {
		return new(DeployCommand), []string{"charm-name", "service-name"}
	},
	func() (cmd.Command, []string) { return new(StatusCommand), nil },
}

// TestEnvironmentInit tests that all commands which accept
// the --environment variable initialise their
// environment name correctly.
func (*CmdSuite) TestEnvironmentInit(c *C) {
	for i, cmdFunc := range EnvironmentInitTests {
		c.Logf("test %d", i)
		com, args := cmdFunc()
		testInit(c, com, args, "")
		assertConnName(c, com, "")

		com, args = cmdFunc()
		testInit(c, com, append(args, "-e", "walthamstow"), "")
		assertConnName(c, com, "walthamstow")

		com, args = cmdFunc()
		testInit(c, com, append(args, "--environment", "walthamstow"), "")
		assertConnName(c, com, "walthamstow")

		com, args = cmdFunc()
		testInit(c, com, append(args, "hotdog"), "unrecognized args.*")
	}
}

func runCommand(com cmd.Command, args ...string) (opc chan dummy.Operation, errc chan error) {
	errc = make(chan error, 1)
	opc = make(chan dummy.Operation, 200)
	dummy.Reset()
	dummy.Listen(opc)
	go func() {
		// signal that we're done with this ops channel.
		defer dummy.Listen(nil)

		err := com.Init(newFlagSet(), args)
		if err != nil {
			errc <- err
			return
		}

		err = com.Run(cmd.DefaultContext())
		errc <- err
	}()
	return
}

func (*CmdSuite) TestDestroyEnvironmentCommand(c *C) {
	// normal destroy
	opc, errc := runCommand(new(DestroyEnvironmentCommand))
	c.Check(<-errc, IsNil)
	c.Check((<-opc).(dummy.OpDestroy).Env, Equals, "peckham")

	// destroy with broken environment
	opc, errc = runCommand(new(DestroyEnvironmentCommand), "-e", "brokenenv")
	c.Check(<-opc, IsNil)
	c.Check(<-errc, ErrorMatches, "dummy.Destroy is broken")
	c.Check(<-opc, IsNil)
}

var deployTests = []struct {
	args []string
	com  *DeployCommand
}{
	{
		[]string{"charm-name"},
		&DeployCommand{},
	}, {
		[]string{"charm-name", "service-name"},
		&DeployCommand{ServiceName: "service-name"},
	}, {
		[]string{"--repository", "/path/to/another-repo", "charm-name"},
		&DeployCommand{RepoPath: "/path/to/another-repo"},
	}, {
		[]string{"--upgrade", "charm-name"},
		&DeployCommand{BumpRevision: true},
	}, {
		[]string{"-u", "charm-name"},
		&DeployCommand{BumpRevision: true},
	}, {
		[]string{"--num-units", "33", "charm-name"},
		&DeployCommand{NumUnits: 33},
	}, {
		[]string{"-n", "104", "charm-name"},
		&DeployCommand{NumUnits: 104},
	},
}

func initExpectations(com *DeployCommand) {
	if com.CharmName == "" {
		com.CharmName = "charm-name"
	}
	if com.NumUnits == 0 {
		com.NumUnits = 1
	}
	if com.RepoPath == "" {
		com.RepoPath = "/path/to/repo"
	}
}

func initDeployCommand(args ...string) (*DeployCommand, error) {
	com := &DeployCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestDeployCommandInit(c *C) {
	defer os.Setenv("JUJU_REPOSITORY", os.Getenv("JUJU_REPOSITORY"))
	os.Setenv("JUJU_REPOSITORY", "/path/to/repo")

	for _, t := range deployTests {
		initExpectations(t.com)
		com, err := initDeployCommand(t.args...)
		c.Assert(err, IsNil)
		c.Assert(com, DeepEquals, t.com)
	}

	// test relative --config path
	ctx := &cmd.Context{c.MkDir(), nil, nil, nil}
	expected := []byte("test: data")
	path := ctx.AbsPath("testconfig.yaml")
	file, err := os.Create(path)
	c.Assert(err, IsNil)
	_, err = file.Write(expected)
	c.Assert(err, IsNil)
	file.Close()

	com, err := initDeployCommand("--config", "testconfig.yaml", "charm-name")
	c.Assert(err, IsNil)
	actual, err := com.Config.Read(ctx)
	c.Assert(err, IsNil)
	c.Assert(expected, DeepEquals, actual)

	// missing args
	_, err = initDeployCommand()
	c.Assert(err, ErrorMatches, "no charm specified")

	// bad unit count
	_, err = initDeployCommand("charm-name", "--num-units", "0")
	c.Assert(err, ErrorMatches, "must deploy at least one unit")
	_, err = initDeployCommand("charm-name", "-n", "0")
	c.Assert(err, ErrorMatches, "must deploy at least one unit")

	// environment tested elsewhere
}

func initAddUnitCommand(args ...string) (*AddUnitCommand, error) {
	com := &AddUnitCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestAddUnitCommandInit(c *C) {
	// missing args
	_, err := initAddUnitCommand()
	c.Assert(err, ErrorMatches, "no service specified")

	// bad unit count
	_, err = initAddUnitCommand("service-name", "--num-units", "0")
	c.Assert(err, ErrorMatches, "must add at least one unit")
	_, err = initAddUnitCommand("service-name", "-n", "0")
	c.Assert(err, ErrorMatches, "must add at least one unit")

	// environment tested elsewhere
}

func initExposeCommand(args ...string) (*ExposeCommand, error) {
	com := &ExposeCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestExposeCommandInit(c *C) {
	// missing args
	_, err := initExposeCommand()
	c.Assert(err, ErrorMatches, "no service name specified")

	// environment tested elsewhere
}

func initUnexposeCommand(args ...string) (*UnexposeCommand, error) {
	com := &UnexposeCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestUnexposeCommandInit(c *C) {
	// missing args
	_, err := initUnexposeCommand()
	c.Assert(err, ErrorMatches, "no service name specified")

	// environment tested elsewhere
}

func initSSHCommand(args ...string) (*SSHCommand, error) {
	com := &SSHCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestSSHCommandInit(c *C) {
	// missing args
	_, err := initSSHCommand()
	c.Assert(err, ErrorMatches, "no service name specified")
}

func initSCPCommand(args ...string) (*SCPCommand, error) {
	com := &SCPCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestSCPCommandInit(c *C) {
	// missing args
	_, err := initSCPCommand()
	c.Assert(err, ErrorMatches, "at least two arguments required")

	// not enough args
	_, err = initSCPCommand("mysql/0:foo")
	c.Assert(err, ErrorMatches, "at least two arguments required")
}

func initGetCommand(args ...string) (*GetCommand, error) {
	com := &GetCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestGetCommandInit(c *C) {
	// missing args
	_, err := initGetCommand()
	c.Assert(err, ErrorMatches, "no service name specified")
}

func initSetCommand(args ...string) (*SetCommand, error) {
	com := &SetCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestSetCommandInit(c *C) {
	// missing args
	_, err := initSetCommand()
	c.Assert(err, ErrorMatches, "no service name specified")
	// missing service name
	_, err = initSetCommand("name=cow")
	c.Assert(err, ErrorMatches, "no service name specified")

	// test --config path
	expected := []byte("this: is some test data")
	ctx := &cmd.Context{c.MkDir(), nil, nil, nil}
	path := ctx.AbsPath("testconfig.yaml")
	file, err := os.Create(path)
	c.Assert(err, IsNil)
	_, err = file.Write(expected)
	c.Assert(err, IsNil)
	file.Close()
	com, err := initSetCommand("--config", "testconfig.yaml", "service")
	c.Assert(err, IsNil)
	c.Assert(com.Config.Path, Equals, "testconfig.yaml")
	actual, err := com.Config.Read(ctx)
	c.Assert(err, IsNil)
	c.Assert(actual, DeepEquals, expected)

	// --config path, but no service
	com, err = initSetCommand("--config", "testconfig")
	c.Assert(err, ErrorMatches, "no service name specified")

	// --config and options specified
	com, err = initSetCommand("service", "--config", "testconfig", "bees=")
	c.Assert(err, ErrorMatches, "cannot specify --config when using key=value arguments")
}

func initDestroyUnitCommand(args ...string) (*DestroyUnitCommand, error) {
	com := &DestroyUnitCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestDestroyUnitCommandInit(c *C) {
	// missing args
	_, err := initDestroyUnitCommand()
	c.Assert(err, ErrorMatches, "no service units specified")
	// not a unit
	_, err = initDestroyUnitCommand("seven/nine")
	c.Assert(err, ErrorMatches, "invalid service unit name: \"seven/nine\"")
}
<commit_msg>address review<commit_after>package main

import (
	"io/ioutil"
	"os"
	"reflect"

	"launchpad.net/gnuflag"
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/cmd"
	"launchpad.net/juju-core/environs/dummy"
	"launchpad.net/juju-core/juju/testing"
)

type CmdSuite struct {
	testing.JujuConnSuite
	home fakeHome
}

var _ = Suite(&CmdSuite{})

var envConfig = `
default:
    peckham
environments:
    peckham:
        type: dummy
        state-server: false
        admin-secret: arble
        authorized-keys: i-am-a-key
    walthamstow:
        type: dummy
        state-server: false
        authorized-keys: i-am-a-key
    brokenenv:
        type: dummy
        broken: Bootstrap Destroy
        state-server: false
        authorized-keys: i-am-a-key
`

func (s *CmdSuite) SetUpTest(c *C) {
	s.JujuConnSuite.SetUpTest(c)
	s.home = makeFakeHome(c, "peckham", "walthamstow", "brokenenv")
	err := ioutil.WriteFile(homePath(".juju", "environments.yaml"), []byte(envConfig), 0666)
	c.Assert(err, IsNil)
}

func (s *CmdSuite) TearDownTest(c *C) {
	s.home.restore()
	s.JujuConnSuite.TearDownTest(c)
}

func newFlagSet() *gnuflag.FlagSet {
	return gnuflag.NewFlagSet("", gnuflag.ContinueOnError)
}

// testInit checks that a command initialises correctly
// with the given set of arguments.
func testInit(c *C, com cmd.Command, args []string, errPat string) {
	err := com.Init(newFlagSet(), args)
	if errPat != "" {
		c.Assert(err, ErrorMatches, errPat)
	} else {
		c.Assert(err, IsNil)
	}
}

// assertConnName asserts that the Command is using
// the given environment name.
// Since every command has a different type,
// we use reflection to look at the value of the
// Conn field in the value.
func assertConnName(c *C, com cmd.Command, name string) {
	v := reflect.ValueOf(com).Elem().FieldByName("EnvName")
	c.Assert(v.IsValid(), Equals, true)
	c.Assert(v.Interface(), Equals, name)
}

// All members of EnvironmentInitTests are tested for the -environment and -e
// flags, and that extra arguments will cause parsing to fail.
var EnvironmentInitTests = []func() (cmd.Command, []string){
	func() (cmd.Command, []string) { return new(BootstrapCommand), nil },
	func() (cmd.Command, []string) { return new(DestroyEnvironmentCommand), nil },
	func() (cmd.Command, []string) {
		return new(DeployCommand), []string{"charm-name", "service-name"}
	},
	func() (cmd.Command, []string) { return new(StatusCommand), nil },
}

// TestEnvironmentInit tests that all commands which accept
// the --environment variable initialise their
// environment name correctly.
func (*CmdSuite) TestEnvironmentInit(c *C) {
	for i, cmdFunc := range EnvironmentInitTests {
		c.Logf("test %d", i)
		com, args := cmdFunc()
		testInit(c, com, args, "")
		assertConnName(c, com, "")

		com, args = cmdFunc()
		testInit(c, com, append(args, "-e", "walthamstow"), "")
		assertConnName(c, com, "walthamstow")

		com, args = cmdFunc()
		testInit(c, com, append(args, "--environment", "walthamstow"), "")
		assertConnName(c, com, "walthamstow")

		com, args = cmdFunc()
		testInit(c, com, append(args, "hotdog"), "unrecognized args.*")
	}
}

func runCommand(com cmd.Command, args ...string) (opc chan dummy.Operation, errc chan error) {
	errc = make(chan error, 1)
	opc = make(chan dummy.Operation, 200)
	dummy.Reset()
	dummy.Listen(opc)
	go func() {
		// signal that we're done with this ops channel.
		defer dummy.Listen(nil)

		err := com.Init(newFlagSet(), args)
		if err != nil {
			errc <- err
			return
		}

		err = com.Run(cmd.DefaultContext())
		errc <- err
	}()
	return
}

func (*CmdSuite) TestDestroyEnvironmentCommand(c *C) {
	// normal destroy
	opc, errc := runCommand(new(DestroyEnvironmentCommand))
	c.Check(<-errc, IsNil)
	c.Check((<-opc).(dummy.OpDestroy).Env, Equals, "peckham")

	// destroy with broken environment
	opc, errc = runCommand(new(DestroyEnvironmentCommand), "-e", "brokenenv")
	c.Check(<-opc, IsNil)
	c.Check(<-errc, ErrorMatches, "dummy.Destroy is broken")
	c.Check(<-opc, IsNil)
}

var deployTests = []struct {
	args []string
	com  *DeployCommand
}{
	{
		[]string{"charm-name"},
		&DeployCommand{},
	}, {
		[]string{"charm-name", "service-name"},
		&DeployCommand{ServiceName: "service-name"},
	}, {
		[]string{"--repository", "/path/to/another-repo", "charm-name"},
		&DeployCommand{RepoPath: "/path/to/another-repo"},
	}, {
		[]string{"--upgrade", "charm-name"},
		&DeployCommand{BumpRevision: true},
	}, {
		[]string{"-u", "charm-name"},
		&DeployCommand{BumpRevision: true},
	}, {
		[]string{"--num-units", "33", "charm-name"},
		&DeployCommand{NumUnits: 33},
	}, {
		[]string{"-n", "104", "charm-name"},
		&DeployCommand{NumUnits: 104},
	},
}

func initExpectations(com *DeployCommand) {
	if com.CharmName == "" {
		com.CharmName = "charm-name"
	}
	if com.NumUnits == 0 {
		com.NumUnits = 1
	}
	if com.RepoPath == "" {
		com.RepoPath = "/path/to/repo"
	}
}

func initDeployCommand(args ...string) (*DeployCommand, error) {
	com := &DeployCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestDeployCommandInit(c *C) {
	defer os.Setenv("JUJU_REPOSITORY", os.Getenv("JUJU_REPOSITORY"))
	os.Setenv("JUJU_REPOSITORY", "/path/to/repo")

	for _, t := range deployTests {
		initExpectations(t.com)
		com, err := initDeployCommand(t.args...)
		c.Assert(err, IsNil)
		c.Assert(com, DeepEquals, t.com)
	}

	// test relative --config path
	ctx := &cmd.Context{c.MkDir(), nil, nil, nil}
	expected := []byte("test: data")
	path := ctx.AbsPath("testconfig.yaml")
	file, err := os.Create(path)
	c.Assert(err, IsNil)
	_, err = file.Write(expected)
	c.Assert(err, IsNil)
	file.Close()

	com, err := initDeployCommand("--config", "testconfig.yaml", "charm-name")
	c.Assert(err, IsNil)
	actual, err := com.Config.Read(ctx)
	c.Assert(err, IsNil)
	c.Assert(expected, DeepEquals, actual)

	// missing args
	_, err = initDeployCommand()
	c.Assert(err, ErrorMatches, "no charm specified")

	// bad unit count
	_, err = initDeployCommand("charm-name", "--num-units", "0")
	c.Assert(err, ErrorMatches, "must deploy at least one unit")
	_, err = initDeployCommand("charm-name", "-n", "0")
	c.Assert(err, ErrorMatches, "must deploy at least one unit")

	// environment tested elsewhere
}

func initAddUnitCommand(args ...string) (*AddUnitCommand, error) {
	com := &AddUnitCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestAddUnitCommandInit(c *C) {
	// missing args
	_, err := initAddUnitCommand()
	c.Assert(err, ErrorMatches, "no service specified")

	// bad unit count
	_, err = initAddUnitCommand("service-name", "--num-units", "0")
	c.Assert(err, ErrorMatches, "must add at least one unit")
	_, err = initAddUnitCommand("service-name", "-n", "0")
	c.Assert(err, ErrorMatches, "must add at least one unit")

	// environment tested elsewhere
}

func initExposeCommand(args ...string) (*ExposeCommand, error) {
	com := &ExposeCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestExposeCommandInit(c *C) {
	// missing args
	_, err := initExposeCommand()
	c.Assert(err, ErrorMatches, "no service name specified")

	// environment tested elsewhere
}

func initUnexposeCommand(args ...string) (*UnexposeCommand, error) {
	com := &UnexposeCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestUnexposeCommandInit(c *C) {
	// missing args
	_, err := initUnexposeCommand()
	c.Assert(err, ErrorMatches, "no service name specified")

	// environment tested elsewhere
}

func initSSHCommand(args ...string) (*SSHCommand, error) {
	com := &SSHCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestSSHCommandInit(c *C) {
	// missing args
	_, err := initSSHCommand()
	c.Assert(err, ErrorMatches, "no service name specified")
}

func initSCPCommand(args ...string) (*SCPCommand, error) {
	com := &SCPCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestSCPCommandInit(c *C) {
	// missing args
	_, err := initSCPCommand()
	c.Assert(err, ErrorMatches, "at least two arguments required")

	// not enough args
	_, err = initSCPCommand("mysql/0:foo")
	c.Assert(err, ErrorMatches, "at least two arguments required")
}

func initGetCommand(args ...string) (*GetCommand, error) {
	com := &GetCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestGetCommandInit(c *C) {
	// missing args
	_, err := initGetCommand()
	c.Assert(err, ErrorMatches, "no service name specified")
}

func initSetCommand(args ...string) (*SetCommand, error) {
	com := &SetCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestSetCommandInit(c *C) {
	// missing args
	_, err := initSetCommand()
	c.Assert(err, ErrorMatches, "no service name specified")
	// missing service name
	_, err = initSetCommand("name=cow")
	c.Assert(err, ErrorMatches, "no service name specified")

	// test --config path
	expected := []byte("this: is some test data")
	ctx := &cmd.Context{c.MkDir(), nil, nil, nil}
	path := ctx.AbsPath("testconfig.yaml")
	file, err := os.Create(path)
	c.Assert(err, IsNil)
	_, err = file.Write(expected)
	c.Assert(err, IsNil)
	file.Close()
	com, err := initSetCommand("--config", "testconfig.yaml", "service")
	c.Assert(err, IsNil)
	c.Assert(com.Config.Path, Equals, "testconfig.yaml")
	actual, err := com.Config.Read(ctx)
	c.Assert(err, IsNil)
	c.Assert(actual, DeepEquals, expected)

	// --config path, but no service
	com, err = initSetCommand("--config", "testconfig")
	c.Assert(err, ErrorMatches, "no service name specified")

	// --config and options specified
	com, err = initSetCommand("service", "--config", "testconfig", "bees=")
	c.Assert(err, ErrorMatches, "cannot specify --config when using key=value arguments")
}

func initDestroyUnitCommand(args ...string) (*DestroyUnitCommand, error) {
	com := &DestroyUnitCommand{}
	return com, com.Init(newFlagSet(), args)
}

func (*CmdSuite) TestDestroyUnitCommandInit(c *C) {
	// missing args
	_, err := initDestroyUnitCommand()
	c.Assert(err, ErrorMatches, "no service units specified")
	// not a unit
	_, err = initDestroyUnitCommand("seven/nine")
	c.Assert(err, ErrorMatches, `invalid service unit name: "seven/nine"`)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"
	"time"

	"github.com/client9/misspell"
)

var (
	defaultWrite *template.Template
	defaultRead  *template.Template

	stdout *log.Logger
	debug  *log.Logger
)

const (
	defaultWriteTmpl = `{{ .Filename }}:{{ .Line }}:{{ .Column }}:corrected "{{ js .Original }}" to "{{ js .Corrected }}"`
	defaultReadTmpl  = `{{ .Filename }}:{{ .Line }}:{{ .Column }}:found "{{ js .Original }}" a misspelling of "{{ js .Corrected }}"`
	csvTmpl          = `{{ printf "%q" .Filename }},{{ .Line }},{{ .Column }},{{ .Original }},{{ .Corrected }}`
	csvHeader        = `file,line,column,typo,corrected`
	sqliteTmpl       = `INSERT INTO misspell VALUES({{ printf "%q" .Filename }},{{ .Line }},{{ .Column }},{{ printf "%q" .Original }},{{ printf "%q" .Corrected }});`
	sqliteHeader     = `PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE misspell(
	"file" TEXT, "line" INTEGER, "column" INTEGER, "typo" TEXT, "corrected" TEXT
);`
	sqliteFooter = "COMMIT;"
)

func worker(writeit bool, r *misspell.Replacer, mode string, files <-chan string, results chan<- int) {
	count := 0
	for filename := range files {
		orig, err := misspell.ReadTextFile(filename)
		if err != nil {
			log.Println(err)
			continue
		}
		if len(orig) == 0 {
			continue
		}

		updated, changes := r.Replace(orig)

		if len(changes) == 0 {
			continue
		}
		count += len(changes)
		for _, diff := range changes {
			// add in filename
			diff.Filename = filename

			// output can be done by doing multiple goroutines
			// and can clobber os.Stdout.
			//
			// the log package can be used simultaneously from multiple goroutines
			var output bytes.Buffer
			if writeit {
				defaultWrite.Execute(&output, diff)
			} else {
				defaultRead.Execute(&output, diff)
			}

			// goroutine-safe print to os.Stdout
			stdout.Println(output.String())
		}

		if writeit {
			ioutil.WriteFile(filename, []byte(updated), 0)
		}
	}
	results <- count
}

func main() {
	t := time.Now()
	var (
		workers   = flag.Int("j", 0, "Number of workers, 0 = number of CPUs")
		writeit   = flag.Bool("w", false, "Overwrite file with corrections (default is just to display)")
		quietFlag = flag.Bool("quiet", false, "Do not emit misspelling output")
		outFlag   = flag.String("o", "stdout", "output file or [stderr|stdout|]")
		format    = flag.String("f", "", "'csv', 'sqlite3' or custom Golang template for output")
		ignores   = flag.String("i", "", "ignore the following corrections, comma separated")
		locale    = flag.String("locale", "", "Correct spellings using locale perferances for US or UK.  Default is to use a neutral variety of English.  Setting locale to US will correct the British spelling of 'colour' to 'color'")
		mode      = flag.String("source", "auto", "Source mode: auto=guess, go=golang source, text=plain or markdown-like text")
		debugFlag = flag.Bool("debug", false, "Debug matching, very slow")
		exitError = flag.Bool("error", false, "Exit with 2 if misspelling found")
	)
	flag.Parse()

	if *debugFlag {
		debug = log.New(os.Stderr, "DEBUG ", 0)
	} else {
		debug = log.New(ioutil.Discard, "", 0)
	}

	r := misspell.Replacer{
		Replacements: misspell.DictMain,
		Debug:        *debugFlag,
	}
	//
	// Figure out regional variations
	//
	switch strings.ToUpper(*locale) {
	case "":
		// nothing
	case "US":
		r.AddRuleList(misspell.DictAmerican)
	case "UK", "GB":
		r.AddRuleList(misspell.DictBritish)
	case "NZ", "AU", "CA":
		log.Fatalf("Help wanted.  https://github.com/client9/misspell/issues/6")
	default:
		log.Fatalf("Unknow locale: %q", *locale)
	}

	//
	// Stuff to ignore
	//
	if len(*ignores) > 0 {
		r.RemoveRule(strings.Split(*ignores, ","))
	}

	//
	// Source input mode
	//
	switch *mode {
	case "auto":
	case "go":
	case "text":
	default:
		log.Fatalf("Mode must be one of auto=guess, go=golang source, text=plain or markdown-like text")
	}

	//
	// Custom output
	//
	switch {
	case *format == "csv":
		tmpl := template.Must(template.New("csv").Parse(csvTmpl))
		defaultWrite = tmpl
		defaultRead = tmpl
		stdout.Println(csvHeader)
	case *format == "sqlite" || *format == "sqlite3":
		tmpl := template.Must(template.New("sqlite3").Parse(sqliteTmpl))
		defaultWrite = tmpl
		defaultRead = tmpl
		stdout.Println(sqliteHeader)
	case len(*format) > 0:
		t, err := template.New("custom").Parse(*format)
		if err != nil {
			log.Fatalf("Unable to compile log format: %s", err)
		}
		defaultWrite = t
		defaultRead = t
	default: // format == ""
		defaultWrite = template.Must(template.New("defaultWrite").Parse(defaultWriteTmpl))
		defaultRead = template.Must(template.New("defaultRead").Parse(defaultReadTmpl))
	}

	// we cant't just write to os.Stdout directly since we have multiple goroutine
	// all writing at the same time causing broken output.  Log is routine safe.
	// we see it so it doesn't use a prefix or include a time stamp.
	switch {
	case *quietFlag || *outFlag == "/dev/null":
		stdout = log.New(ioutil.Discard, "", 0)
	case *outFlag == "/dev/stderr" || *outFlag == "stderr":
		stdout = log.New(os.Stderr, "", 0)
	case *outFlag == "/dev/stdout" || *outFlag == "stdout":
		stdout = log.New(os.Stdout, "", 0)
	case *outFlag == "" || *outFlag == "-":
		stdout = log.New(os.Stdout, "", 0)
	default:
		fo, err := os.Create(*outFlag)
		if err != nil {
			log.Fatalf("unable to create outfile %q: %s", *outFlag, err)
		}
		defer fo.Close()
		stdout = log.New(fo, "", 0)
	}

	//
	// Number of Workers / CPU to use
	//
	if *workers < 0 {
		log.Fatalf("-j must >= 0")
	}
	if *workers == 0 {
		*workers = runtime.NumCPU()
	}
	if *debugFlag {
		*workers = 1
	}

	//
	// Done with Flags.
	//  Compile the Replacer and process files
	//
	r.Compile()

	args := flag.Args()
	debug.Printf("initialization complete in %v", time.Since(t))

	// unix style pipes: different output module
	// stdout: output of corrected text
	// stderr: output of log lines
	if len(args) == 0 {
		raw, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			log.Fatalf("Unable to read stdin")
		}
		orig := string(raw)
		updated, changes := r.Replace(orig)
		if !*quietFlag {
			for _, diff := range changes {
				diff.Filename = "stdin"
				var output bytes.Buffer
				if *writeit {
					defaultWrite.Execute(&output, diff)
				} else {
					defaultRead.Execute(&output, diff)
				}
				stdout.Println(output.String())
			}
		}
		if *writeit {
			stdout.Println(updated)
		}
		switch *format {
		case "sqlite", "sqlite3":
			stdout.Println(sqliteFooter)
		}
		return
	}

	c := make(chan string, 64)
	results := make(chan int, *workers)

	for i := 0; i < *workers; i++ {
		go worker(*writeit, &r, *mode, c, results)
	}

	for _, filename := range args {
		filepath.Walk(filename, func(path string, info os.FileInfo, err error) error {
			if err == nil && !info.IsDir() {
				c <- path
			}
			return nil
		})
	}
	close(c)

	count := 0
	for i := 0; i < *workers; i++ {
		changed := <-results
		count += changed
	}

	switch *format {
	case "sqlite", "sqlite3":
		stdout.Println(sqliteFooter)
	}

	if count != 0 && *exitError {
		// log.Printf("Got %d", count)
		// error
		os.Exit(2)
	}
}
<commit_msg>Close #71 flexible output<commit_after>package main

import (
	"bytes"
	"flag"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"
	"time"

	"github.com/client9/misspell"
)

var (
	defaultWrite *template.Template
	defaultRead  *template.Template

	stdout *log.Logger
	debug  *log.Logger
)

const (
	defaultWriteTmpl = `{{ .Filename }}:{{ .Line }}:{{ .Column }}:corrected "{{ js .Original }}" to "{{ js .Corrected }}"`
	defaultReadTmpl  = `{{ .Filename }}:{{ .Line }}:{{ .Column }}:found "{{ js .Original }}" a misspelling of "{{ js .Corrected }}"`
	csvTmpl          = `{{ printf "%q" .Filename }},{{ .Line }},{{ .Column }},{{ .Original }},{{ .Corrected }}`
	csvHeader        = `file,line,column,typo,corrected`
	sqliteTmpl       = `INSERT INTO misspell VALUES({{ printf "%q" .Filename }},{{ .Line }},{{ .Column }},{{ printf "%q" .Original }},{{ printf "%q" .Corrected }});`
	sqliteHeader     = `PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE misspell(
	"file" TEXT, "line" INTEGER, "column" INTEGER, "typo" TEXT, "corrected" TEXT
);`
	sqliteFooter = "COMMIT;"
)

func worker(writeit bool, r *misspell.Replacer, mode string, files <-chan string, results chan<- int) {
	count := 0
	for filename := range files {
		orig, err := misspell.ReadTextFile(filename)
		if err != nil {
			log.Println(err)
			continue
		}
		if len(orig) == 0 {
			continue
		}

		updated, changes := r.Replace(orig)

		if len(changes) == 0 {
			continue
		}
		count += len(changes)
		for _, diff := range changes {
			// add in filename
			diff.Filename = filename

			// output can be done by doing multiple goroutines
			// and can clobber os.Stdout.
			//
			// the log package can be used simultaneously from multiple goroutines
			var output bytes.Buffer
			if writeit {
				defaultWrite.Execute(&output, diff)
			} else {
				defaultRead.Execute(&output, diff)
			}

			// goroutine-safe print to os.Stdout
			stdout.Println(output.String())
		}

		if writeit {
			ioutil.WriteFile(filename, []byte(updated), 0)
		}
	}
	results <- count
}

func main() {
	t := time.Now()
	var (
		workers   = flag.Int("j", 0, "Number of workers, 0 = number of CPUs")
		writeit   = flag.Bool("w", false, "Overwrite file with corrections (default is just to display)")
		quietFlag = flag.Bool("q", false, "Do not emit misspelling output")
		outFlag   = flag.String("o", "stdout", "output file or [stderr|stdout|]")
		format    = flag.String("f", "", "'csv', 'sqlite3' or custom Golang template for output")
		ignores   = flag.String("i", "", "ignore the following corrections, comma separated")
		locale    = flag.String("locale", "", "Correct spellings using locale perferances for US or UK.  Default is to use a neutral variety of English.  Setting locale to US will correct the British spelling of 'colour' to 'color'")
		mode      = flag.String("source", "auto", "Source mode: auto=guess, go=golang source, text=plain or markdown-like text")
		debugFlag = flag.Bool("debug", false, "Debug matching, very slow")
		exitError = flag.Bool("error", false, "Exit with 2 if misspelling found")
	)
	flag.Parse()

	if *debugFlag {
		debug = log.New(os.Stderr, "DEBUG ", 0)
	} else {
		debug = log.New(ioutil.Discard, "", 0)
	}

	r := misspell.Replacer{
		Replacements: misspell.DictMain,
		Debug:        *debugFlag,
	}
	//
	// Figure out regional variations
	//
	switch strings.ToUpper(*locale) {
	case "":
		// nothing
	case "US":
		r.AddRuleList(misspell.DictAmerican)
	case "UK", "GB":
		r.AddRuleList(misspell.DictBritish)
	case "NZ", "AU", "CA":
		log.Fatalf("Help wanted.  https://github.com/client9/misspell/issues/6")
	default:
		log.Fatalf("Unknow locale: %q", *locale)
	}

	//
	// Stuff to ignore
	//
	if len(*ignores) > 0 {
		r.RemoveRule(strings.Split(*ignores, ","))
	}

	//
	// Source input mode
	//
	switch *mode {
	case "auto":
	case "go":
	case "text":
	default:
		log.Fatalf("Mode must be one of auto=guess, go=golang source, text=plain or markdown-like text")
	}

	//
	// Custom output
	//
	switch {
	case *format == "csv":
		tmpl := template.Must(template.New("csv").Parse(csvTmpl))
		defaultWrite = tmpl
		defaultRead = tmpl
		stdout.Println(csvHeader)
	case *format == "sqlite" || *format == "sqlite3":
		tmpl := template.Must(template.New("sqlite3").Parse(sqliteTmpl))
		defaultWrite = tmpl
		defaultRead = tmpl
		stdout.Println(sqliteHeader)
	case len(*format) > 0:
		t, err := template.New("custom").Parse(*format)
		if err != nil {
			log.Fatalf("Unable to compile log format: %s", err)
		}
		defaultWrite = t
		defaultRead = t
	default: // format == ""
		defaultWrite = template.Must(template.New("defaultWrite").Parse(defaultWriteTmpl))
		defaultRead = template.Must(template.New("defaultRead").Parse(defaultReadTmpl))
	}

	// we cant't just write to os.Stdout directly since we have multiple goroutine
	// all writing at the same time causing broken output.  Log is routine safe.
	// we see it so it doesn't use a prefix or include a time stamp.
	switch {
	case *quietFlag || *outFlag == "/dev/null":
		stdout = log.New(ioutil.Discard, "", 0)
	case *outFlag == "/dev/stderr" || *outFlag == "stderr":
		stdout = log.New(os.Stderr, "", 0)
	case *outFlag == "/dev/stdout" || *outFlag == "stdout":
		stdout = log.New(os.Stdout, "", 0)
	case *outFlag == "" || *outFlag == "-":
		stdout = log.New(os.Stdout, "", 0)
	default:
		fo, err := os.Create(*outFlag)
		if err != nil {
			log.Fatalf("unable to create outfile %q: %s", *outFlag, err)
		}
		defer fo.Close()
		stdout = log.New(fo, "", 0)
	}

	//
	// Number of Workers / CPU to use
	//
	if *workers < 0 {
		log.Fatalf("-j must >= 0")
	}
	if *workers == 0 {
		*workers = runtime.NumCPU()
	}
	if *debugFlag {
		*workers = 1
	}

	//
	// Done with Flags.
	//  Compile the Replacer and process files
	//
	r.Compile()

	args := flag.Args()
	debug.Printf("initialization complete in %v", time.Since(t))

	// unix style pipes: different output module
	// stdout: output of corrected text
	// stderr: output of log lines
	if len(args) == 0 {
		raw, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			log.Fatalf("Unable to read stdin")
		}
		orig := string(raw)
		updated, changes := r.Replace(orig)
		if !*quietFlag {
			for _, diff := range changes {
				diff.Filename = "stdin"
				var output bytes.Buffer
				if *writeit {
					defaultWrite.Execute(&output, diff)
				} else {
					defaultRead.Execute(&output, diff)
				}
				stdout.Println(output.String())
			}
		}
		if *writeit {
			stdout.Println(updated)
		}
		switch *format {
		case "sqlite", "sqlite3":
			stdout.Println(sqliteFooter)
		}
		return
	}

	c := make(chan string, 64)
	results := make(chan int, *workers)

	for i := 0; i < *workers; i++ {
		go worker(*writeit, &r, *mode, c, results)
	}

	for _, filename := range args {
		filepath.Walk(filename, func(path string, info os.FileInfo, err error) error {
			if err == nil && !info.IsDir() {
				c <- path
			}
			return nil
		})
	}
	close(c)

	count := 0
	for i := 0; i < *workers; i++ {
		changed := <-results
		count += changed
	}

	switch *format {
	case "sqlite", "sqlite3":
		stdout.Println(sqliteFooter)
	}

	if count != 0 && *exitError {
		// log.Printf("Got %d", count)
		// error
		os.Exit(2)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"errors"
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/cenkalti/backoff"
	"github.com/go-kit/kit/metrics"
	"github.com/relab/raft"
	"github.com/relab/raft/commonpb"
	"github.com/relab/rkv/rkvpb"
	"google.golang.org/grpc"
)

type client struct {
	l             *uint64
	currentLeader uint64
	servers       []rkvpb.RKVClient

	inFlight int64

	id  uint64
	seq uint64

	zipf *z

	s *stats

	payload int
	lat     *raft.Latency
}

// z wraps rand.Zipf with a mutex.
type z struct {
	sync.Mutex
	*rand.Zipf
}

func (zz *z) Uint64() uint64 {
	zz.Lock()
	i := zz.Zipf.Uint64()
	zz.Unlock()
	return i
}

func newClient(leader *uint64, servers []string, zipf *rand.Zipf, s *stats, payload int, lat *raft.Latency) (*client, error) {
	conns := make([]rkvpb.RKVClient, len(servers))

	for i, server := range servers {
		cc, err := grpc.Dial(server, grpc.WithBlock(), grpc.WithInsecure())

		if err != nil {
			return nil, err
		}

		conns[i] = rkvpb.NewRKVClient(cc)
	}

	c := &client{
		l:       leader,
		servers: conns,
		zipf:    &z{Zipf: zipf},
		s:       s,
		payload: payload,
		lat:     lat,
	}

	res, err := c.register()

	if err != nil {
		return nil, err
	}

	c.id = res.ClientID
	c.seq = 1

	return c, nil
}

const (
	requestTimeout = 10 * time.Minute
	maxInFlight    = 50000
)

func newBackOff() *backoff.ExponentialBackOff {
	b := backoff.NewExponentialBackOff()
	b.InitialInterval = 1 * time.Millisecond
	b.RandomizationFactor = 0.5
	b.Multiplier = 1.5
	b.MaxInterval = 1 * time.Second
	b.MaxElapsedTime = 15 * time.Minute
	b.Reset()
	return b
}

func notify(err error, d time.Duration) {
	// TODO For debugging.
	fmt.Println(err, d)
}

// ErrMaxInFlightReached indicates that there are too many messages in-flight.
var ErrMaxInFlightReached = errors.New("reached max in-flight")

func (c *client) addServer(serverID uint64) (*commonpb.ReconfResponse, error) {
	return c.reconf(serverID, commonpb.ReconfAdd)
}

func (c *client) removeServer(serverID uint64) (*commonpb.ReconfResponse, error) {
	return c.reconf(serverID, commonpb.ReconfRemove)
}

func (c *client) reconf(serverID uint64, reconfType commonpb.ReconfType) (*commonpb.ReconfResponse, error) {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	var res *commonpb.ReconfResponse

	op := func() error {
		var err error
		prevLeader := c.getLeader()
		res, err = c.servers[prevLeader].Reconf(ctx, &commonpb.ReconfRequest{
			ServerID:   serverID,
			ReconfType: reconfType,
		})

		if err != nil {
			c.nextLeader(prevLeader)
			return err
		}

		return nil
	}

	err := backoff.RetryNotify(op, backoff.WithContext(newBackOff(), ctx), notify)

	return res, err
}

func (c *client) register() (*rkvpb.RegisterResponse, error) {
	if atomic.LoadInt64(&c.inFlight) > maxInFlight {
		return nil, ErrMaxInFlightReached
	}
	atomic.AddInt64(&c.inFlight, 1)
	defer func() {
		atomic.AddInt64(&c.inFlight, -1)
	}()

	c.s.writeReqs.Add(1)
	timer := metrics.NewTimer(c.s.writeLatency)

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	var res *rkvpb.RegisterResponse

	op := func() error {
		var err error
		prevLeader := c.getLeader()
		res, err = c.servers[prevLeader].Register(ctx, &rkvpb.RegisterRequest{})

		if err != nil {
			c.nextLeader(prevLeader)
			return err
		}

		c.s.writes.Add(1)
		timer.ObserveDuration()
		return nil

	}

	err := backoff.RetryNotify(op, backoff.WithContext(newBackOff(), ctx), notify)

	return res, err
}

func (c *client) lookup() (*rkvpb.LookupResponse, error) {
	if atomic.LoadInt64(&c.inFlight) > maxInFlight {
		return nil, ErrMaxInFlightReached
	}
	atomic.AddInt64(&c.inFlight, 1)
	defer func() {
		atomic.AddInt64(&c.inFlight, -1)
	}()

	c.s.readReqs.Add(1)
	timer := metrics.NewTimer(c.s.readLatency)

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	var res *rkvpb.LookupResponse

	req := &rkvpb.LookupRequest{
		Key: c.randPayload(),
	}

	op := func() error {
		var err error
		prevLeader := c.getLeader()
		res, err = c.servers[prevLeader].Lookup(ctx, req)

		if err != nil {
			c.nextLeader(prevLeader)
			return err
		}

		c.s.reads.Add(1)
		timer.ObserveDuration()
		return nil
	}

	err := backoff.RetryNotify(op, backoff.WithContext(newBackOff(), ctx), notify)

	return res, err
}

func (c *client) insert() (*rkvpb.InsertResponse, error) {
	if atomic.LoadInt64(&c.inFlight) > maxInFlight {
		return nil, ErrMaxInFlightReached
	}
	atomic.AddInt64(&c.inFlight, 1)
	defer func() {
		atomic.AddInt64(&c.inFlight, -1)
	}()

	c.s.writeReqs.Add(1)
	timer := metrics.NewTimer(c.s.writeLatency)
	start := time.Now()

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	var res *rkvpb.InsertResponse

	req := &rkvpb.InsertRequest{
		ClientID:  c.id,
		ClientSeq: atomic.AddUint64(&c.seq, 1),
		Key:       c.randPayload(),
		Value:     c.randPayload(),
	}

	op := func() error {
		var err error
		prevLeader := c.getLeader()
		res, err = c.servers[prevLeader].Insert(ctx, req)

		if err != nil {
			c.nextLeader(prevLeader)
			return err
		}

		c.s.writes.Add(1)
		timer.ObserveDuration()
		c.lat.Record(start)
		return nil
	}

	err := backoff.RetryNotify(op, backoff.WithContext(newBackOff(), ctx), notify)

	return res, err
}

func (c *client) nextLeader(prevLeader uint64) {
	newLeader := (prevLeader + 1) % uint64(len(c.servers))
	atomic.CompareAndSwapUint64(c.l, prevLeader, newLeader)
}

func (c *client) getLeader() uint64 {
	c.currentLeader = atomic.LoadUint64(c.l)
	return c.currentLeader
}

func (c *client) randPayload() string {
	return strconv.FormatUint(c.zipf.Uint64(), 10) + strings.Repeat("X", c.payload)
}
<commit_msg>rkvctl/client.go: Remove log spam<commit_after>package main

import (
	"context"
	"errors"
	"math/rand"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/cenkalti/backoff"
	"github.com/go-kit/kit/metrics"
	"github.com/relab/raft"
	"github.com/relab/raft/commonpb"
	"github.com/relab/rkv/rkvpb"
	"google.golang.org/grpc"
)

type client struct {
	l             *uint64
	currentLeader uint64
	servers       []rkvpb.RKVClient

	inFlight int64

	id  uint64
	seq uint64

	zipf *z

	s *stats

	payload int
	lat     *raft.Latency
}

// z wraps rand.Zipf with a mutex.
type z struct {
	sync.Mutex
	*rand.Zipf
}

func (zz *z) Uint64() uint64 {
	zz.Lock()
	i := zz.Zipf.Uint64()
	zz.Unlock()
	return i
}

func newClient(leader *uint64, servers []string, zipf *rand.Zipf, s *stats, payload int, lat *raft.Latency) (*client, error) {
	conns := make([]rkvpb.RKVClient, len(servers))

	for i, server := range servers {
		cc, err := grpc.Dial(server, grpc.WithBlock(), grpc.WithInsecure())

		if err != nil {
			return nil, err
		}

		conns[i] = rkvpb.NewRKVClient(cc)
	}

	c := &client{
		l:       leader,
		servers: conns,
		zipf:    &z{Zipf: zipf},
		s:       s,
		payload: payload,
		lat:     lat,
	}

	res, err := c.register()

	if err != nil {
		return nil, err
	}

	c.id = res.ClientID
	c.seq = 1

	return c, nil
}

const (
	requestTimeout = 10 * time.Minute
	maxInFlight    = 50000
)

func newBackOff() *backoff.ExponentialBackOff {
	b := backoff.NewExponentialBackOff()
	b.InitialInterval = 1 * time.Millisecond
	b.RandomizationFactor = 0.5
	b.Multiplier = 1.5
	b.MaxInterval = 1 * time.Second
	b.MaxElapsedTime = 15 * time.Minute
	b.Reset()
	return b
}

func notify(err error, d time.Duration) {
	// TODO For debugging.
}

// ErrMaxInFlightReached indicates that there are too many messages in-flight.
var ErrMaxInFlightReached = errors.New("reached max in-flight")

func (c *client) addServer(serverID uint64) (*commonpb.ReconfResponse, error) {
	return c.reconf(serverID, commonpb.ReconfAdd)
}

func (c *client) removeServer(serverID uint64) (*commonpb.ReconfResponse, error) {
	return c.reconf(serverID, commonpb.ReconfRemove)
}

func (c *client) reconf(serverID uint64, reconfType commonpb.ReconfType) (*commonpb.ReconfResponse, error) {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	var res *commonpb.ReconfResponse

	op := func() error {
		var err error
		prevLeader := c.getLeader()
		res, err = c.servers[prevLeader].Reconf(ctx, &commonpb.ReconfRequest{
			ServerID:   serverID,
			ReconfType: reconfType,
		})

		if err != nil {
			c.nextLeader(prevLeader)
			return err
		}

		return nil
	}

	err := backoff.RetryNotify(op, backoff.WithContext(newBackOff(), ctx), notify)

	return res, err
}

func (c *client) register() (*rkvpb.RegisterResponse, error) {
	if atomic.LoadInt64(&c.inFlight) > maxInFlight {
		return nil, ErrMaxInFlightReached
	}
	atomic.AddInt64(&c.inFlight, 1)
	defer func() {
		atomic.AddInt64(&c.inFlight, -1)
	}()

	c.s.writeReqs.Add(1)
	timer := metrics.NewTimer(c.s.writeLatency)

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	var res *rkvpb.RegisterResponse

	op := func() error {
		var err error
		prevLeader := c.getLeader()
		res, err = c.servers[prevLeader].Register(ctx, &rkvpb.RegisterRequest{})

		if err != nil {
			c.nextLeader(prevLeader)
			return err
		}

		c.s.writes.Add(1)
		timer.ObserveDuration()
		return nil

	}

	err := backoff.RetryNotify(op, backoff.WithContext(newBackOff(), ctx), notify)

	return res, err
}

func (c *client) lookup() (*rkvpb.LookupResponse, error) {
	if atomic.LoadInt64(&c.inFlight) > maxInFlight {
		return nil, ErrMaxInFlightReached
	}
	atomic.AddInt64(&c.inFlight, 1)
	defer func() {
		atomic.AddInt64(&c.inFlight, -1)
	}()

	c.s.readReqs.Add(1)
	timer := metrics.NewTimer(c.s.readLatency)

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	var res *rkvpb.LookupResponse

	req := &rkvpb.LookupRequest{
		Key: c.randPayload(),
	}

	op := func() error {
		var err error
		prevLeader := c.getLeader()
		res, err = c.servers[prevLeader].Lookup(ctx, req)

		if err != nil {
			c.nextLeader(prevLeader)
			return err
		}

		c.s.reads.Add(1)
		timer.ObserveDuration()
		return nil
	}

	err := backoff.RetryNotify(op, backoff.WithContext(newBackOff(), ctx), notify)

	return res, err
}

func (c *client) insert() (*rkvpb.InsertResponse, error) {
	if atomic.LoadInt64(&c.inFlight) > maxInFlight {
		return nil, ErrMaxInFlightReached
	}
	atomic.AddInt64(&c.inFlight, 1)
	defer func() {
		atomic.AddInt64(&c.inFlight, -1)
	}()

	c.s.writeReqs.Add(1)
	timer := metrics.NewTimer(c.s.writeLatency)
	start := time.Now()

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()

	var res *rkvpb.InsertResponse

	req := &rkvpb.InsertRequest{
		ClientID:  c.id,
		ClientSeq: atomic.AddUint64(&c.seq, 1),
		Key:       c.randPayload(),
		Value:     c.randPayload(),
	}

	op := func() error {
		var err error
		prevLeader := c.getLeader()
		res, err = c.servers[prevLeader].Insert(ctx, req)

		if err != nil {
			c.nextLeader(prevLeader)
			return err
		}

		c.s.writes.Add(1)
		timer.ObserveDuration()
		c.lat.Record(start)
		return nil
	}

	err := backoff.RetryNotify(op, backoff.WithContext(newBackOff(), ctx), notify)

	return res, err
}

func (c *client) nextLeader(prevLeader uint64) {
	newLeader := (prevLeader + 1) % uint64(len(c.servers))
	atomic.CompareAndSwapUint64(c.l, prevLeader, newLeader)
}

func (c *client) getLeader() uint64 {
	c.currentLeader = atomic.LoadUint64(c.l)
	return c.currentLeader
}

func (c *client) randPayload() string {
	return strconv.FormatUint(c.zipf.Uint64(), 10) + strings.Repeat("X", c.payload)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/gophergala/matching-snuggies/slicerjob"
)

func main() {
	server := flag.String("server", "localhost:8888", "snuggied server address")
	slicerBackend := flag.String("backend", "slic3r", "backend slicer")
	slicerPreset := flag.String("preset", "hiQ", "specify a configuration preset for the backend")
	gcodeDest := flag.String("o", "", "specify an output gcode filename")
	flag.Parse()

	if flag.NArg() < 1 {
		log.Fatalf("missing argument: mesh file")
	}
	meshpath := flag.Arg(0)

	client := &Client{
		ServerAddr: *server,
	}

	// start intercepting signals from the operating system
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

	// send files to the slicer to be printed and poll the slicer until the job
	// has completed.
	log.Printf("sending file(s) to snuggied server at %v", *server)
	job, err := client.SliceFile(*slicerBackend, *slicerPreset, meshpath)
	if err != nil {
		log.Fatalf("sending files: %v", err)
	}
	tick := time.Tick(100 * time.Millisecond)
	for job.Status != slicerjob.Complete {
		// TODO: retry with exponential backoff on network failure
		select {
		case s := <-sig:
			// stop intercepting signals. if the job cancellation is taking too
			// long let the future signals terminate the process naturally.
			signal.Stop(sig)
			log.Printf("signal: %v", s)
			err := client.Cancel(job)
			if err != nil {
				log.Printf("failed to cancel job: %v", err)
			}
			log.Printf("slicing job canceled")
			return
		case <-tick:
			job, err = client.SlicerStatus(job)
			if err != nil {
				log.Fatalf("waiting: %v", err)
			}
		}
	}

	// download gcode from the slicer and write to the specified file.
	log.Printf("retreiving gcode file")
	r, err := client.GCode(job)
	if err != nil {
		log.Fatalf("gcode: %v", err)
	}
	defer r.Close()
	var f *os.File
	if *gcodeDest == "" {
		f = os.Stdout
	} else {
		f, err = os.Create(*gcodeDest)
		if err != nil {
			log.Panic(err)
		}
	}
	defer func() {
		err := f.Close()
		if err != nil {
			log.Panic(err)
		}
	}()
	_, err = io.Copy(f, r)
	if err != nil {
		log.Panic(err)
	}
}

type Client struct {
	Client     *http.Client
	ServerAddr string
	HTTPS      bool
}

// SliceFiles tells the server to slice the specified paths.
func (c *Client) SliceFile(backend, preset string, path string) (*slicerjob.Job, error) {
	// check that a mesh file is given as the first argument and open it
	// so it may to encode in the form.
	if !IsMeshFile(path) {
		log.Fatalf("path is not a mesh file: %v", path)
	}
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	// write the multipart form out to a temporary file.  the temporary
	// file is closed and unlinked when the function terminates.
	tmp, err := ioutil.TempFile("", "matching-snuggies-post-")
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()
	bodyw := multipart.NewWriter(tmp)
	err = c.writeJobForm(bodyw, backend, preset, path, f)
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}

	// seek back to the beginning of the form and POST it to the slicer
	// server.  decode a slicerjob.Job from successful responses.
	var job *slicerjob.Job
	_, err = tmp.Seek(0, 0)
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}
	url := c.url("/slicer/jobs")
	log.Printf("POST %v", url)
	resp, err := c.client().Post(url, bodyw.FormDataContentType(), tmp)
	if err != nil {
		return nil, fmt.Errorf("POST /slicer/jobs: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		p, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("response: %q (%v)", p, http.StatusText(resp.StatusCode))
	}
	err = json.NewDecoder(resp.Body).Decode(&job)
	if err != nil {
		return nil, fmt.Errorf("response: %v", err)
	}
	return job, nil
}

func (c *Client) writeJobForm(w *multipart.Writer, backend, preset, filename string, r io.Reader) error {
	err := w.WriteField("slicer", backend)
	if err != nil {
		return err
	}
	err = w.WriteField("preset", preset)
	if err != nil {
		return err
	}
	file, err := w.CreateFormFile("meshfile", filepath.Base(filename))
	if err != nil {
		return err
	}
	_, err = io.Copy(file, r)
	if err != nil {
		return err
	}
	return w.Close()
}

func (c *Client) Cancel(job *slicerjob.Job) error {
	if job.ID == "" {
		return fmt.Errorf("job missing id")
	}
	url := c.url("/slicer/jobs/" + job.ID)
	log.Printf("DELETE %v", url)
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return fmt.Errorf("request: %v", err)
	}
	resp, err := c.client().Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		body, _ := ioutil.ReadAll(resp.Body)
		return fmt.Errorf("HTTP %v: %s", http.StatusText(resp.StatusCode), body)
	}
	return nil
}

// SlicerStatus returns a current copy of the provided job.
func (c *Client) SlicerStatus(job *slicerjob.Job) (*slicerjob.Job, error) {
	if job.ID == "" {
		return nil, fmt.Errorf("job missing id")
	}
	var jobcurr *slicerjob.Job
	url := c.url("/slicer/jobs/" + job.ID)
	log.Printf("GET %v", url)
	resp, err := c.client().Get(url)
	if err != nil {
		return nil, fmt.Errorf("POST /slicer/jobs/: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		p, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("response: %q (%v)", p, http.StatusText(resp.StatusCode))
	}
	err = json.NewDecoder(resp.Body).Decode(&jobcurr)
	if err != nil {
		return nil, fmt.Errorf("response: %v", err)
	}
	return jobcurr, nil
}

// GCode requests the gcode for job.
func (c *Client) GCode(job *slicerjob.Job) (io.ReadCloser, error) {
	url := c.url("/slicer/gcodes/" + job.ID)
	log.Printf("POST %v", url)
	resp, err := c.client().Get(url)
	if err != nil {
		return nil, fmt.Errorf("POST /slicer/jobs/: %v", err)
	}
	if resp.StatusCode != 200 {
		p, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("response: %q (%v)", p, http.StatusText(resp.StatusCode))
	}
	return resp.Body, nil
}

func (c *Client) client() *http.Client {
	if c.Client == nil {
		return http.DefaultClient
	}
	return c.Client
}

func (c *Client) url(pathquery string) string {
	pathquery = strings.TrimPrefix(pathquery, "/")
	scheme := "http"
	if c.HTTPS {
		scheme = "https"
	}
	return scheme + "://" + c.ServerAddr + "/" + pathquery
}

var meshExts = map[string]bool{
	".stl": true,
	".amf": true,
}

func IsMeshFile(path string) bool {
	return meshExts[filepath.Ext(path)]
}
<commit_msg>don't worry about intercepting signals when writing out gcode<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/gophergala/matching-snuggies/slicerjob"
)

func main() {
	server := flag.String("server", "localhost:8888", "snuggied server address")
	slicerBackend := flag.String("backend", "slic3r", "backend slicer")
	slicerPreset := flag.String("preset", "hiQ", "specify a configuration preset for the backend")
	gcodeDest := flag.String("o", "", "specify an output gcode filename")
	flag.Parse()

	if flag.NArg() < 1 {
		log.Fatalf("missing argument: mesh file")
	}
	meshpath := flag.Arg(0)

	client := &Client{
		ServerAddr: *server,
	}

	// start intercepting signals from the operating system
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

	// send files to the slicer to be printed and poll the slicer until the job
	// has completed.
	log.Printf("sending file(s) to snuggied server at %v", *server)
	job, err := client.SliceFile(*slicerBackend, *slicerPreset, meshpath)
	if err != nil {
		log.Fatalf("sending files: %v", err)
	}
	tick := time.Tick(100 * time.Millisecond)
	for job.Status != slicerjob.Complete {
		// TODO: retry with exponential backoff on network failure
		select {
		case s := <-sig:
			// stop intercepting signals. if the job cancellation is taking too
			// long let the future signals terminate the process naturally.
			signal.Stop(sig)
			log.Printf("signal: %v", s)
			err := client.Cancel(job)
			if err != nil {
				log.Printf("failed to cancel job: %v", err)
			}
			log.Printf("slicing job canceled")
			return
		case <-tick:
			job, err = client.SlicerStatus(job)
			if err != nil {
				log.Fatalf("waiting: %v", err)
			}
		}
	}
	// stop intercepting signals because it because much more difficult to stop
	// gracefully while reading gcode from the server.
	signal.Stop(sig)

	// download gcode from the slicer and write to the specified file.
	log.Printf("retreiving gcode file")
	r, err := client.GCode(job)
	if err != nil {
		log.Fatalf("gcode: %v", err)
	}
	defer r.Close()
	var f *os.File
	if *gcodeDest == "" {
		f = os.Stdout
	} else {
		f, err = os.Create(*gcodeDest)
		if err != nil {
			log.Panic(err)
		}
	}
	defer func() {
		err := f.Close()
		if err != nil {
			log.Panic(err)
		}
	}()
	_, err = io.Copy(f, r)
	if err != nil {
		log.Panic(err)
	}
}

type Client struct {
	Client     *http.Client
	ServerAddr string
	HTTPS      bool
}

// SliceFiles tells the server to slice the specified paths.
func (c *Client) SliceFile(backend, preset string, path string) (*slicerjob.Job, error) {
	// check that a mesh file is given as the first argument and open it
	// so it may to encode in the form.
	if !IsMeshFile(path) {
		log.Fatalf("path is not a mesh file: %v", path)
	}
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	// write the multipart form out to a temporary file.  the temporary
	// file is closed and unlinked when the function terminates.
	tmp, err := ioutil.TempFile("", "matching-snuggies-post-")
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()
	bodyw := multipart.NewWriter(tmp)
	err = c.writeJobForm(bodyw, backend, preset, path, f)
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}

	// seek back to the beginning of the form and POST it to the slicer
	// server.  decode a slicerjob.Job from successful responses.
	var job *slicerjob.Job
	_, err = tmp.Seek(0, 0)
	if err != nil {
		return nil, fmt.Errorf("tempfile: %v", err)
	}
	url := c.url("/slicer/jobs")
	log.Printf("POST %v", url)
	resp, err := c.client().Post(url, bodyw.FormDataContentType(), tmp)
	if err != nil {
		return nil, fmt.Errorf("POST /slicer/jobs: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		p, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("response: %q (%v)", p, http.StatusText(resp.StatusCode))
	}
	err = json.NewDecoder(resp.Body).Decode(&job)
	if err != nil {
		return nil, fmt.Errorf("response: %v", err)
	}
	return job, nil
}

func (c *Client) writeJobForm(w *multipart.Writer, backend, preset, filename string, r io.Reader) error {
	err := w.WriteField("slicer", backend)
	if err != nil {
		return err
	}
	err = w.WriteField("preset", preset)
	if err != nil {
		return err
	}
	file, err := w.CreateFormFile("meshfile", filepath.Base(filename))
	if err != nil {
		return err
	}
	_, err = io.Copy(file, r)
	if err != nil {
		return err
	}
	return w.Close()
}

func (c *Client) Cancel(job *slicerjob.Job) error {
	if job.ID == "" {
		return fmt.Errorf("job missing id")
	}
	url := c.url("/slicer/jobs/" + job.ID)
	log.Printf("DELETE %v", url)
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return fmt.Errorf("request: %v", err)
	}
	resp, err := c.client().Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		body, _ := ioutil.ReadAll(resp.Body)
		return fmt.Errorf("HTTP %v: %s", http.StatusText(resp.StatusCode), body)
	}
	return nil
}

// SlicerStatus returns a current copy of the provided job.
func (c *Client) SlicerStatus(job *slicerjob.Job) (*slicerjob.Job, error) {
	if job.ID == "" {
		return nil, fmt.Errorf("job missing id")
	}
	var jobcurr *slicerjob.Job
	url := c.url("/slicer/jobs/" + job.ID)
	log.Printf("GET %v", url)
	resp, err := c.client().Get(url)
	if err != nil {
		return nil, fmt.Errorf("POST /slicer/jobs/: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		p, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("response: %q (%v)", p, http.StatusText(resp.StatusCode))
	}
	err = json.NewDecoder(resp.Body).Decode(&jobcurr)
	if err != nil {
		return nil, fmt.Errorf("response: %v", err)
	}
	return jobcurr, nil
}

// GCode requests the gcode for job.
func (c *Client) GCode(job *slicerjob.Job) (io.ReadCloser, error) {
	url := c.url("/slicer/gcodes/" + job.ID)
	log.Printf("POST %v", url)
	resp, err := c.client().Get(url)
	if err != nil {
		return nil, fmt.Errorf("POST /slicer/jobs/: %v", err)
	}
	if resp.StatusCode != 200 {
		p, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("response: %q (%v)", p, http.StatusText(resp.StatusCode))
	}
	return resp.Body, nil
}

func (c *Client) client() *http.Client {
	if c.Client == nil {
		return http.DefaultClient
	}
	return c.Client
}

func (c *Client) url(pathquery string) string {
	pathquery = strings.TrimPrefix(pathquery, "/")
	scheme := "http"
	if c.HTTPS {
		scheme = "https"
	}
	return scheme + "://" + c.ServerAddr + "/" + pathquery
}

var meshExts = map[string]bool{
	".stl": true,
	".amf": true,
}

func IsMeshFile(path string) bool {
	return meshExts[filepath.Ext(path)]
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/nochso/tocenize"
)

const (
	_ = iota // ignore 0-1
	_
	ExitUsageError // 2 as used by flag
	ExitInputError
)

func main() {
	log.SetFlags(0)
	flag.Usage = func() {
		fmt.Println("tocenize [options] FILE...")
		fmt.Println()
		flag.PrintDefaults()
	}
	job := tocenize.Job{}
	flag.IntVar(&job.MinDepth, "min", 1, "minimum depth")
	flag.IntVar(&job.MaxDepth, "max", 99, "maximum depth")
	flag.BoolVar(&job.Diff, "d", false, "print full diff to stdout")
	flag.BoolVar(&job.Print, "p", false, "print full result to stdout")
	flag.BoolVar(&job.Update, "u", true, "update existing file")
	flag.BoolVar(&tocenize.Verbose, "v", false, "verbose output")
	flag.Parse()

	if flag.NArg() == 0 {
		fmt.Println("too few arguments")
		os.Exit(2)
	}

	for _, path := range flag.Args() {
		log.SetPrefix(path + ": ")
		doc, err := tocenize.NewDocument(path)
		if err != nil {
			log.Println(err)
			continue
		}
		toc := tocenize.NewTOC(doc, job)
		err = doc.Update(toc, job)
		if err != nil {
			log.Println(err)
			continue
		}
	}
}
<commit_msg>Show usage when arguments are missing<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/nochso/tocenize"
)

const (
	_ = iota // ignore 0-1
	_
	ExitUsageError // 2 as used by flag
	ExitInputError
)

func main() {
	log.SetFlags(0)
	flag.Usage = func() {
		fmt.Println("tocenize [options] FILE...")
		fmt.Println()
		flag.PrintDefaults()
	}
	job := tocenize.Job{}
	flag.IntVar(&job.MinDepth, "min", 1, "minimum depth")
	flag.IntVar(&job.MaxDepth, "max", 99, "maximum depth")
	flag.BoolVar(&job.Diff, "d", false, "print full diff to stdout")
	flag.BoolVar(&job.Print, "p", false, "print full result to stdout")
	flag.BoolVar(&job.Update, "u", true, "update existing file")
	flag.BoolVar(&tocenize.Verbose, "v", false, "verbose output")
	flag.Parse()

	if flag.NArg() == 0 {
		log.Println("too few arguments")
		flag.Usage()
		os.Exit(2)
	}

	for _, path := range flag.Args() {
		log.SetPrefix(path + ": ")
		doc, err := tocenize.NewDocument(path)
		if err != nil {
			log.Println(err)
			continue
		}
		toc := tocenize.NewTOC(doc, job)
		err = doc.Update(toc, job)
		if err != nil {
			log.Println(err)
			continue
		}
	}
}
<|endoftext|>


<commit_before>package genre

import (
	"strings"

	"github.com/aerogo/aero"
	"github.com/animenotifier/arn"
	"github.com/animenotifier/notify.moe/components"
	"github.com/animenotifier/notify.moe/utils"
)

const animePerPage = 100

// Get ...
func Get(ctx *aero.Context) string {
	user := utils.GetUser(ctx)
	genreName := ctx.Get("name")
	animes := []*arn.Anime{}

	for anime := range arn.StreamAnime() {
		if containsLowerCase(anime.Genres, genreName) {
			animes = append(animes, anime)
		}
	}

	userScore := averageScore(user, animes)

	arn.SortAnimeByQuality(animes)

	if len(animes) > animePerPage {
		animes = animes[:animePerPage]
	}

	return ctx.HTML(components.Genre(genreName, animes, user, userScore))
}

// containsLowerCase tells you whether the given element exists when all elements are lowercased.
func containsLowerCase(array []string, search string) bool {
	for _, item := range array {
		if strings.ToLower(item) == search {
			return true
		}
	}

	return false
}

// Counts users average score for selected animes
func averageScore(user *arn.User, animes []*arn.Anime) float64 {
	if user == nil {
		return 0
	}

	count := 0.0
	scores := 0.0

	animeList := user.AnimeList()

	for _, anime := range animes {
		userAnime := animeList.Find(anime.ID)

		if userAnime != nil && !userAnime.Rating.IsNotRated() {
			scores += userAnime.Rating.Overall
			count++
		}
	}

	if count == 0.0 {
		return 0
	}

	return scores / count
}
<commit_msg>Changed averageScore comment<commit_after>package genre

import (
	"strings"

	"github.com/aerogo/aero"
	"github.com/animenotifier/arn"
	"github.com/animenotifier/notify.moe/components"
	"github.com/animenotifier/notify.moe/utils"
)

const animePerPage = 100

// Get ...
func Get(ctx *aero.Context) string {
	user := utils.GetUser(ctx)
	genreName := ctx.Get("name")
	animes := []*arn.Anime{}

	for anime := range arn.StreamAnime() {
		if containsLowerCase(anime.Genres, genreName) {
			animes = append(animes, anime)
		}
	}

	userScore := averageScore(user, animes)

	arn.SortAnimeByQuality(animes)

	if len(animes) > animePerPage {
		animes = animes[:animePerPage]
	}

	return ctx.HTML(components.Genre(genreName, animes, user, userScore))
}

// containsLowerCase tells you whether the given element exists when all elements are lowercased.
func containsLowerCase(array []string, search string) bool {
	for _, item := range array {
		if strings.ToLower(item) == search {
			return true
		}
	}

	return false
}

// averageScore counts the user's average score for the given animes.
func averageScore(user *arn.User, animes []*arn.Anime) float64 {
	if user == nil {
		return 0
	}

	count := 0.0
	scores := 0.0

	animeList := user.AnimeList()

	for _, anime := range animes {
		userAnime := animeList.Find(anime.ID)

		if userAnime != nil && !userAnime.Rating.IsNotRated() {
			scores += userAnime.Rating.Overall
			count++
		}
	}

	if count == 0.0 {
		return 0
	}

	return scores / count
}
<|endoftext|>


<commit_before>package cgo

import (
	"fmt"
	"runtime"

	"github.com/mweagle/Sparta"
)

// Main defines the primary handler for transforming an application into a Sparta package.  The
// serviceName is used to uniquely identify your service within a region and will
// be used for subsequent updates.  For provisioning, ensure that you've
// properly configured AWS credentials for the golang SDK.
// See http://docs.aws.amazon.com/sdk-for-go/api/aws/defaults.html#DefaultChainCredentials-constant
// for more information.
func Main(serviceName string,
	serviceDescription string,
	lambdaAWSInfos []*sparta.LambdaAWSInfo,
	api *sparta.API,
	site *sparta.S3Site) error {

	_, file, _, ok := runtime.Caller(1)
	if !ok {
		return fmt.Errorf("Failed to determine caller site for CGO")
	}
	return cgoMain(file,
		serviceName,
		serviceDescription,
		lambdaAWSInfos,
		api,
		site,
		nil)
}

// MainEx is the CGO enabled signature compatible version sparta.MainEx
// function that will attempt to rewrite the input source to be a CGO
// compliant library
func MainEx(serviceName string,
	serviceDescription string,
	lambdaAWSInfos []*sparta.LambdaAWSInfo,
	api *sparta.API,
	site *sparta.S3Site,
	workflowHooks *sparta.WorkflowHooks) error {

	_, file, _, ok := runtime.Caller(1)
	if !ok {
		return fmt.Errorf("Failed to determine caller site for CGO")
	}

	// If this is a "normal" execution, let's try and
	// use the existing golang functions
	return cgoMain(file,
		serviceName,
		serviceDescription,
		lambdaAWSInfos,
		api,
		site,
		nil)
}
<commit_msg>Minor import correction<commit_after>package cgo

import (
	"fmt"
	"runtime"

	sparta "github.com/mweagle/Sparta"
)

// Main defines the primary handler for transforming an application into a Sparta package.  The
// serviceName is used to uniquely identify your service within a region and will
// be used for subsequent updates.  For provisioning, ensure that you've
// properly configured AWS credentials for the golang SDK.
// See http://docs.aws.amazon.com/sdk-for-go/api/aws/defaults.html#DefaultChainCredentials-constant
// for more information.
func Main(serviceName string,
	serviceDescription string,
	lambdaAWSInfos []*sparta.LambdaAWSInfo,
	api *sparta.API,
	site *sparta.S3Site) error {

	_, file, _, ok := runtime.Caller(1)
	if !ok {
		return fmt.Errorf("Failed to determine caller site for CGO")
	}
	return cgoMain(file,
		serviceName,
		serviceDescription,
		lambdaAWSInfos,
		api,
		site,
		nil)
}

// MainEx is the CGO enabled signature compatible version sparta.MainEx
// function that will attempt to rewrite the input source to be a CGO
// compliant library
func MainEx(serviceName string,
	serviceDescription string,
	lambdaAWSInfos []*sparta.LambdaAWSInfo,
	api *sparta.API,
	site *sparta.S3Site,
	workflowHooks *sparta.WorkflowHooks) error {

	_, file, _, ok := runtime.Caller(1)
	if !ok {
		return fmt.Errorf("Failed to determine caller site for CGO")
	}

	// If this is a "normal" execution, let's try and
	// use the existing golang functions
	return cgoMain(file,
		serviceName,
		serviceDescription,
		lambdaAWSInfos,
		api,
		site,
		nil)
}
<|endoftext|>


<commit_before>package channels

import (
	"fmt"
	"io"
	"log"
	"os"

	"github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/format"
	"github.com/mackerelio/mkr/logger"
	"github.com/mackerelio/mkr/mackerelclient"
)

type channelsApp struct {
	client    mackerelclient.Client
	outStream io.Writer
}

func (app *channelsApp) run() error {
	channels, err := app.client.FindChannels()
	if err != nil {
		return err
	}

	format.PrettyPrintJSON(app.outStream, channels)
	return nil
}

func (app *channelsApp) pullChannels(isVerbose bool, optFilePath string) error {
	channels, err := mackerelclient.NewFromContext(c).FindChannels()
	logger.DieIf(err)

	filePath := "channels.json"
	if optFilePath != "" {
		filePath = optFilePath
	}

	channelSaveRules(channels, filePath)

	if isVerbose {
		format.PrettyPrintJSON(os.Stdout, channels)
	}

	logger.Log("info", fmt.Sprintf("Channels are saved to '%s' (%d rules).", filePath, len(channels)))
	return nil
}

func channelSaveRules(rules []*mackerel.Channel, filePath string) error {
	file, err := os.Create(filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	channels := map[string]interface{}{"channels": rules}
	data := format.JSONMarshalIndent(channels, "", "    ") + "\n"

	_, err = file.WriteString(data)
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>fix simple mistake<commit_after>package channels

import (
	"fmt"
	"io"
	"log"
	"os"

	"github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/format"
	"github.com/mackerelio/mkr/logger"
	"github.com/mackerelio/mkr/mackerelclient"
)

type channelsApp struct {
	client    mackerelclient.Client
	outStream io.Writer
}

func (app *channelsApp) run() error {
	channels, err := app.client.FindChannels()
	if err != nil {
		return err
	}

	format.PrettyPrintJSON(app.outStream, channels)
	return nil
}

func (app *channelsApp) pullChannels(isVerbose bool, optFilePath string) error {
	channels, err := app.client.FindChannels()
	logger.DieIf(err)

	filePath := "channels.json"
	if optFilePath != "" {
		filePath = optFilePath
	}

	channelSaveRules(channels, filePath)

	if isVerbose {
		format.PrettyPrintJSON(os.Stdout, channels)
	}

	logger.Log("info", fmt.Sprintf("Channels are saved to '%s' (%d rules).", filePath, len(channels)))
	return nil
}

func channelSaveRules(rules []*mackerel.Channel, filePath string) error {
	file, err := os.Create(filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	channels := map[string]interface{}{"channels": rules}
	data := format.JSONMarshalIndent(channels, "", "    ") + "\n"

	_, err = file.WriteString(data)
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package packet

import (
	"errors"
)

// Encode4b6b returns the 4b/6b encoding of the given data.
func Encode4b6b(src []byte) []byte {
	// 2 input bytes produce 3 output bytes.
	// Odd final input byte, if any, produces 2 output bytes.
	n := len(src)
	dst := make([]byte, 3*(n/2)+2*(n%2))
	for i, j := 0, 0; i < n; i, j = i+2, j+3 {
		x := src[i]
		a := encode4b[hi(4, x)]
		b := encode4b[lo(4, x)]
		dst[j] = a<<2 | hi(4, b)
		var c, d byte
		if i+1 < n {
			y := src[i+1]
			c = encode4b[hi(4, y)]
			d = encode4b[lo(4, y)]
			dst[j+2] = lo(2, c)<<6 | d
		}
		dst[j+1] = lo(4, b)<<4 | hi(6, c)
	}
	return dst
}

// ErrDecoding indicates a 6b/4b decoding failure.
var ErrDecoding = errors.New("6b/4b decoding failure")

// Decode6b4b returns the 6b/4b decoding of the given data.
func Decode6b4b(src []byte) ([]byte, error) {
	n := len(src)
	// Check for valid packet length.
	if n%3 == 1 {
		return nil, ErrDecoding
	}
	// 3 input bytes produce 2 output bytes.
	// Final 2 input bytes, if any, produce 1 output byte.
	dst := make([]byte, 2*(n/3)+(n%3)/2)
	for i, j := 0, 0; i < n; i, j = i+3, j+2 {
		if i+1 >= n {
			return dst, ErrDecoding // shouldn't happen
		}
		x := src[i]
		y := src[i+1]
		a := decode6b[hi(6, x)]
		b := decode6b[lo(2, x)<<4|hi(4, y)]
		if a == 0xFF || b == 0xFF {
			return dst, ErrDecoding
		}
		dst[j] = a<<4 | b
		if i+2 < n {
			z := src[i+2]
			c := decode6b[lo(4, y)<<2|hi(2, z)]
			d := decode6b[lo(6, z)]
			if c == 0xFF || d == 0xFF {
				return dst, ErrDecoding
			}
			dst[j+1] = c<<4 | d
		}
	}
	return dst, nil
}

func hi(n, x byte) byte {
	return x >> (8 - n)
}

func lo(n, x byte) byte {
	return x & (1<<n - 1)
}

var (
	encode4b = []byte{
		0x00: 0x15, 0x01: 0x31, 0x02: 0x32, 0x03: 0x23,
		0x04: 0x34, 0x05: 0x25, 0x06: 0x26, 0x07: 0x16,
		0x08: 0x1A, 0x09: 0x19, 0x0A: 0x2A, 0x0B: 0x0B,
		0x0C: 0x2C, 0x0D: 0x0D, 0x0E: 0x0E, 0x0F: 0x1C,
	}

	// Inverse of encode4b table, with 0xFF indicating an undefined value.
	decode6b = []byte{
		0x00: 0xFF, 0x01: 0xFF, 0x02: 0xFF, 0x03: 0xFF,
		0x04: 0xFF, 0x05: 0xFF, 0x06: 0xFF, 0x07: 0xFF,
		0x08: 0xFF, 0x09: 0xFF, 0x0A: 0xFF, 0x0B: 0x0B,
		0x0C: 0xFF, 0x0D: 0x0D, 0x0E: 0x0E, 0x0F: 0xFF,
		0x10: 0xFF, 0x11: 0xFF, 0x12: 0xFF, 0x13: 0xFF,
		0x14: 0xFF, 0x15: 0x00, 0x16: 0x07, 0x17: 0xFF,
		0x18: 0xFF, 0x19: 0x09, 0x1A: 0x08, 0x1B: 0xFF,
		0x1C: 0x0F, 0x1D: 0xFF, 0x1E: 0xFF, 0x1F: 0xFF,
		0x20: 0xFF, 0x21: 0xFF, 0x22: 0xFF, 0x23: 0x03,
		0x24: 0xFF, 0x25: 0x05, 0x26: 0x06, 0x27: 0xFF,
		0x28: 0xFF, 0x29: 0xFF, 0x2A: 0x0A, 0x2B: 0xFF,
		0x2C: 0x0C, 0x2D: 0xFF, 0x2E: 0xFF, 0x2F: 0xFF,
		0x30: 0xFF, 0x31: 0x01, 0x32: 0x02, 0x33: 0xFF,
		0x34: 0x04, 0x35: 0xFF, 0x36: 0xFF, 0x37: 0xFF,
		0x38: 0xFF, 0x39: 0xFF, 0x3A: 0xFF, 0x3B: 0xFF,
		0x3C: 0xFF, 0x3D: 0xFF, 0x3E: 0xFF, 0x3F: 0xFF,
	}
)
<commit_msg>Fill final nibble with 5 for odd-length data<commit_after>package packet

import (
	"errors"
)

// Encode4b6b returns the 4b/6b encoding of the given data.
func Encode4b6b(src []byte) []byte {
	// 2 input bytes produce 3 output bytes.
	// Odd final input byte, if any, produces 2 output bytes.
	n := len(src)
	dst := make([]byte, 3*(n/2)+2*(n%2))
	for i, j := 0, 0; i < n; i, j = i+2, j+3 {
		x := src[i]
		a := encode4b[hi(4, x)]
		b := encode4b[lo(4, x)]
		dst[j] = a<<2 | hi(4, b)
		if i+1 < n {
			y := src[i+1]
			c := encode4b[hi(4, y)]
			d := encode4b[lo(4, y)]
			dst[j+1] = lo(4, b)<<4 | hi(6, c)
			dst[j+2] = lo(2, c)<<6 | d
		} else {
			// Fill final nibble with 5 to match pump behavior.
			dst[j+1] = lo(4, b)<<4 | 0x5
		}
	}
	return dst
}

// ErrDecoding indicates a 6b/4b decoding failure.
var ErrDecoding = errors.New("6b/4b decoding failure")

// Decode6b4b returns the 6b/4b decoding of the given data.
func Decode6b4b(src []byte) ([]byte, error) {
	n := len(src)
	// Check for valid packet length.
	if n%3 == 1 {
		return nil, ErrDecoding
	}
	// 3 input bytes produce 2 output bytes.
	// Final 2 input bytes, if any, produce 1 output byte.
	dst := make([]byte, 2*(n/3)+(n%3)/2)
	for i, j := 0, 0; i < n; i, j = i+3, j+2 {
		if i+1 >= n {
			return dst, ErrDecoding // shouldn't happen
		}
		x := src[i]
		y := src[i+1]
		a := decode6b[hi(6, x)]
		b := decode6b[lo(2, x)<<4|hi(4, y)]
		if a == 0xFF || b == 0xFF {
			return dst, ErrDecoding
		}
		dst[j] = a<<4 | b
		if i+2 < n {
			z := src[i+2]
			c := decode6b[lo(4, y)<<2|hi(2, z)]
			d := decode6b[lo(6, z)]
			if c == 0xFF || d == 0xFF {
				return dst, ErrDecoding
			}
			dst[j+1] = c<<4 | d
		}
	}
	return dst, nil
}

func hi(n, x byte) byte {
	return x >> (8 - n)
}

func lo(n, x byte) byte {
	return x & (1<<n - 1)
}

var (
	encode4b = []byte{
		0x00: 0x15, 0x01: 0x31, 0x02: 0x32, 0x03: 0x23,
		0x04: 0x34, 0x05: 0x25, 0x06: 0x26, 0x07: 0x16,
		0x08: 0x1A, 0x09: 0x19, 0x0A: 0x2A, 0x0B: 0x0B,
		0x0C: 0x2C, 0x0D: 0x0D, 0x0E: 0x0E, 0x0F: 0x1C,
	}

	// Inverse of encode4b table, with 0xFF indicating an undefined value.
	decode6b = []byte{
		0x00: 0xFF, 0x01: 0xFF, 0x02: 0xFF, 0x03: 0xFF,
		0x04: 0xFF, 0x05: 0xFF, 0x06: 0xFF, 0x07: 0xFF,
		0x08: 0xFF, 0x09: 0xFF, 0x0A: 0xFF, 0x0B: 0x0B,
		0x0C: 0xFF, 0x0D: 0x0D, 0x0E: 0x0E, 0x0F: 0xFF,
		0x10: 0xFF, 0x11: 0xFF, 0x12: 0xFF, 0x13: 0xFF,
		0x14: 0xFF, 0x15: 0x00, 0x16: 0x07, 0x17: 0xFF,
		0x18: 0xFF, 0x19: 0x09, 0x1A: 0x08, 0x1B: 0xFF,
		0x1C: 0x0F, 0x1D: 0xFF, 0x1E: 0xFF, 0x1F: 0xFF,
		0x20: 0xFF, 0x21: 0xFF, 0x22: 0xFF, 0x23: 0x03,
		0x24: 0xFF, 0x25: 0x05, 0x26: 0x06, 0x27: 0xFF,
		0x28: 0xFF, 0x29: 0xFF, 0x2A: 0x0A, 0x2B: 0xFF,
		0x2C: 0x0C, 0x2D: 0xFF, 0x2E: 0xFF, 0x2F: 0xFF,
		0x30: 0xFF, 0x31: 0x01, 0x32: 0x02, 0x33: 0xFF,
		0x34: 0x04, 0x35: 0xFF, 0x36: 0xFF, 0x37: 0xFF,
		0x38: 0xFF, 0x39: 0xFF, 0x3A: 0xFF, 0x3B: 0xFF,
		0x3C: 0xFF, 0x3D: 0xFF, 0x3E: 0xFF, 0x3F: 0xFF,
	}
)
<|endoftext|>


<commit_before>package chat

// FIXME: Would be sweet if we could piggyback on a cli parser or something.

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/shazow/ssh-chat/chat/message"
	"github.com/shazow/ssh-chat/internal/sanitize"
	"github.com/shazow/ssh-chat/set"
)

// ErrInvalidCommand is the error returned when an invalid command is issued.
var ErrInvalidCommand = errors.New("invalid command")

// ErrNoOwner is the error returned when a command is given without an owner.
var ErrNoOwner = errors.New("command without owner")

// ErrMissingArg is the error returned when a command is performed without the necessary
// number of arguments.
var ErrMissingArg = errors.New("missing argument")

// ErrMissingPrefix is the error returned when a command is added without a prefix.
var ErrMissingPrefix = errors.New("command missing prefix")

// Command is a definition of a handler for a command.
type Command struct {
	Prefix     string // The command's key, such as /foo
	PrefixHelp string // Extra help regarding arguments
	Help       string // help text, if omitted, command is hidden from /help
	Op         bool   // does the command require Op permissions?

	// Handler for the command
	Handler func(*Room, message.CommandMsg) error
}

// Commands is a registry of available commands.
type Commands map[string]*Command

// Add will register a command. If help string is empty, it will be hidden from
// Help().
func (c Commands) Add(cmd Command) error {
	if cmd.Prefix == "" {
		return ErrMissingPrefix
	}

	c[cmd.Prefix] = &cmd
	return nil
}

// Alias will add another command for the same handler, won't get added to help.
func (c Commands) Alias(command string, alias string) error {
	cmd, ok := c[command]
	if !ok {
		return ErrInvalidCommand
	}
	c[alias] = cmd
	return nil
}

// Run executes a command message.
func (c Commands) Run(room *Room, msg message.CommandMsg) error {
	if msg.From() == nil {
		return ErrNoOwner
	}

	cmd, ok := c[msg.Command()]
	if !ok {
		return ErrInvalidCommand
	}

	return cmd.Handler(room, msg)
}

// Help will return collated help text as one string.
func (c Commands) Help(showOp bool) string {
	// Filter by op
	op := []*Command{}
	normal := []*Command{}
	for _, cmd := range c {
		if cmd.Op {
			op = append(op, cmd)
		} else {
			normal = append(normal, cmd)
		}
	}
	help := "Available commands:" + message.Newline + NewCommandsHelp(normal).String()
	if showOp {
		help += message.Newline + "-> Operator commands:" + message.Newline + NewCommandsHelp(op).String()
	}
	return help
}

var timeformatDatetime = "2006-01-02 15:04:05"

var timeformatTime = "15:04"

var defaultCommands *Commands

func init() {
	defaultCommands = &Commands{}
	InitCommands(defaultCommands)
}

// InitCommands injects default commands into a Commands registry.
func InitCommands(c *Commands) {
	c.Add(Command{
		Prefix: "/help",
		Handler: func(room *Room, msg message.CommandMsg) error {
			op := room.IsOp(msg.From())
			room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix: "/me",
		Handler: func(room *Room, msg message.CommandMsg) error {
			me := strings.TrimLeft(msg.Body(), "/me")
			if me == "" {
				me = "is at a loss for words."
			} else {
				me = me[1:]
			}

			room.Send(message.NewEmoteMsg(me, msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix: "/exit",
		Help:   "Exit the chat.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			msg.From().Close()
			return nil
		},
	})
	c.Alias("/exit", "/quit")

	c.Add(Command{
		Prefix:     "/nick",
		PrefixHelp: "NAME",
		Help:       "Rename yourself.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			args := msg.Args()
			if len(args) != 1 {
				return ErrMissingArg
			}
			u := msg.From()

			member, ok := room.MemberByID(u.ID())
			if !ok {
				return errors.New("failed to find member")
			}

			oldID := member.ID()
			newID := sanitize.Name(args[0])
			if newID == oldID {
				return errors.New("new name is the same as the original")
			}
			member.SetID(newID)
			err := room.Rename(oldID, member)
			if err != nil {
				member.SetID(oldID)
				return err
			}
			return nil
		},
	})

	c.Add(Command{
		Prefix: "/names",
		Help:   "List users who are connected.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			theme := msg.From().Config().Theme

			colorize := func(u *message.User) string {
				return theme.ColorName(u)
			}

			if theme == nil {
				colorize = func(u *message.User) string {
					return u.Name()
				}
			}

			names := room.Members.ListPrefix("")
			colNames := make([]string, len(names))
			for i, uname := range names {
				colNames[i] = colorize(uname.Value().(*Member).User)
			}

			body := fmt.Sprintf("%d connected: %s", len(colNames), strings.Join(colNames, ", "))
			room.Send(message.NewSystemMsg(body, msg.From()))
			return nil
		},
	})
	c.Alias("/names", "/list")

	c.Add(Command{
		Prefix:     "/theme",
		PrefixHelp: "[colors|...]",
		Help:       "Set your color theme.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			user := msg.From()
			args := msg.Args()
			cfg := user.Config()
			if len(args) == 0 {
				theme := "plain"
				if cfg.Theme != nil {
					theme = cfg.Theme.ID()
				}
				var output strings.Builder
				fmt.Fprintf(&output, "Current theme: %s%s", theme, message.Newline)
				fmt.Fprintf(&output, "   Themes available: ")

				for i, t := range message.Themes {
					output.WriteString(t.ID())
					if i < len(message.Themes)-1 {
						output.WriteString(", ")
					}
				}
				room.Send(message.NewSystemMsg(output.String(), user))
				return nil
			}

			id := args[0]
			for _, t := range message.Themes {
				if t.ID() == id {
					cfg.Theme = &t
					user.SetConfig(cfg)
					body := fmt.Sprintf("Set theme: %s", id)
					room.Send(message.NewSystemMsg(body, user))
					return nil
				}
			}
			return errors.New("theme not found")
		},
	})

	c.Add(Command{
		Prefix: "/quiet",
		Help:   "Silence room announcements.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			u := msg.From()
			cfg := u.Config()
			cfg.Quiet = !cfg.Quiet
			u.SetConfig(cfg)

			var body string
			if cfg.Quiet {
				body = "Quiet mode is toggled ON"
			} else {
				body = "Quiet mode is toggled OFF"
			}
			room.Send(message.NewSystemMsg(body, u))
			return nil
		},
	})

	c.Add(Command{
		Prefix:     "/slap",
		PrefixHelp: "NAME",
		Handler: func(room *Room, msg message.CommandMsg) error {
			var me string
			args := msg.Args()
			if len(args) == 0 {
				me = "slaps themselves around a bit with a large trout."
			} else {
				me = fmt.Sprintf("slaps %s around a bit with a large trout.", strings.Join(args, " "))
			}

			room.Send(message.NewEmoteMsg(me, msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix: "/shrug",
		Handler: func(room *Room, msg message.CommandMsg) error {
			room.Send(message.NewEmoteMsg(`¯\_(ツ)_/¯`, msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix:     "/timestamp",
		PrefixHelp: "[time|datetime]",
		Help:       "Prefix messages with a timestamp. You can also provide the UTC offset: /timestamp time +5h45m",
		Handler: func(room *Room, msg message.CommandMsg) error {
			u := msg.From()
			cfg := u.Config()

			args := msg.Args()
			mode := ""
			if len(args) >= 1 {
				mode = args[0]
			}
			if len(args) >= 2 {
				// FIXME: This is an annoying format to demand from users, but
				// hopefully we can make it a non-primary flow if we add GeoIP
				// someday.
				offset, err := time.ParseDuration(args[1])
				if err != nil {
					return err
				}
				cfg.Timezone = time.FixedZone("", int(offset.Seconds()))
			}

			switch mode {
			case "time":
				cfg.Timeformat = &timeformatTime
			case "datetime":
				cfg.Timeformat = &timeformatDatetime
			case "":
				// Toggle
				if cfg.Timeformat != nil {
					cfg.Timeformat = nil
				} else {
					cfg.Timeformat = &timeformatTime
				}
			case "off":
				cfg.Timeformat = nil
			default:
				return errors.New("timestamp value must be one of: time, datetime, off")
			}

			u.SetConfig(cfg)

			var body string
			if cfg.Timeformat != nil {
				if cfg.Timezone != nil {
					tzname := time.Now().In(cfg.Timezone).Format("MST")
					body = fmt.Sprintf("Timestamp is toggled ON, timezone is %q", tzname)
				} else {
					body = "Timestamp is toggled ON, timezone is UTC"
				}
			} else {
				body = "Timestamp is toggled OFF"
			}
			room.Send(message.NewSystemMsg(body, u))
			return nil
		},
	})

	c.Add(Command{
		Prefix:     "/ignore",
		PrefixHelp: "[USER]",
		Help:       "Hide messages from USER, /unignore USER to stop hiding.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/ignore"))
			if id == "" {
				// Print ignored names, if any.
				var names []string
				msg.From().Ignored.Each(func(_ string, item set.Item) error {
					names = append(names, item.Key())
					return nil
				})

				var systemMsg string
				if len(names) == 0 {
					systemMsg = "0 users ignored."
				} else {
					systemMsg = fmt.Sprintf("%d ignored: %s", len(names), strings.Join(names, ", "))
				}

				room.Send(message.NewSystemMsg(systemMsg, msg.From()))
				return nil
			}

			if id == msg.From().ID() {
				return errors.New("cannot ignore self")
			}
			target, ok := room.MemberByID(id)
			if !ok {
				return fmt.Errorf("user not found: %s", id)
			}

			err := msg.From().Ignored.Add(set.Itemize(id, target))
			if err == set.ErrCollision {
				return fmt.Errorf("user already ignored: %s", id)
			} else if err != nil {
				return err
			}

			room.Send(message.NewSystemMsg(fmt.Sprintf("Ignoring: %s", target.Name()), msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix:     "/unignore",
		PrefixHelp: "USER",
		Handler: func(room *Room, msg message.CommandMsg) error {
			id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/unignore"))
			if id == "" {
				return errors.New("must specify user")
			}

			if err := msg.From().Ignored.Remove(id); err != nil {
				return err
			}

			room.Send(message.NewSystemMsg(fmt.Sprintf("No longer ignoring: %s", id), msg.From()))
			return nil
		},
	})
}
<commit_msg>sorting nicks on /names and /list<commit_after>package chat

// FIXME: Would be sweet if we could piggyback on a cli parser or something.

import (
	"errors"
	"fmt"
	"strings"
	"time"
	"sort"

	"github.com/shazow/ssh-chat/chat/message"
	"github.com/shazow/ssh-chat/internal/sanitize"
	"github.com/shazow/ssh-chat/set"
)

// ErrInvalidCommand is the error returned when an invalid command is issued.
var ErrInvalidCommand = errors.New("invalid command")

// ErrNoOwner is the error returned when a command is given without an owner.
var ErrNoOwner = errors.New("command without owner")

// ErrMissingArg is the error returned when a command is performed without the necessary
// number of arguments.
var ErrMissingArg = errors.New("missing argument")

// ErrMissingPrefix is the error returned when a command is added without a prefix.
var ErrMissingPrefix = errors.New("command missing prefix")

// Command is a definition of a handler for a command.
type Command struct {
	Prefix     string // The command's key, such as /foo
	PrefixHelp string // Extra help regarding arguments
	Help       string // help text, if omitted, command is hidden from /help
	Op         bool   // does the command require Op permissions?

	// Handler for the command
	Handler func(*Room, message.CommandMsg) error
}

// Commands is a registry of available commands.
type Commands map[string]*Command

// Add will register a command. If help string is empty, it will be hidden from
// Help().
func (c Commands) Add(cmd Command) error {
	if cmd.Prefix == "" {
		return ErrMissingPrefix
	}

	c[cmd.Prefix] = &cmd
	return nil
}

// Alias will add another command for the same handler, won't get added to help.
func (c Commands) Alias(command string, alias string) error {
	cmd, ok := c[command]
	if !ok {
		return ErrInvalidCommand
	}
	c[alias] = cmd
	return nil
}

// Run executes a command message.
func (c Commands) Run(room *Room, msg message.CommandMsg) error {
	if msg.From() == nil {
		return ErrNoOwner
	}

	cmd, ok := c[msg.Command()]
	if !ok {
		return ErrInvalidCommand
	}

	return cmd.Handler(room, msg)
}

// Help will return collated help text as one string.
func (c Commands) Help(showOp bool) string {
	// Filter by op
	op := []*Command{}
	normal := []*Command{}
	for _, cmd := range c {
		if cmd.Op {
			op = append(op, cmd)
		} else {
			normal = append(normal, cmd)
		}
	}
	help := "Available commands:" + message.Newline + NewCommandsHelp(normal).String()
	if showOp {
		help += message.Newline + "-> Operator commands:" + message.Newline + NewCommandsHelp(op).String()
	}
	return help
}

var timeformatDatetime = "2006-01-02 15:04:05"

var timeformatTime = "15:04"

var defaultCommands *Commands

func init() {
	defaultCommands = &Commands{}
	InitCommands(defaultCommands)
}

// InitCommands injects default commands into a Commands registry.
func InitCommands(c *Commands) {
	c.Add(Command{
		Prefix: "/help",
		Handler: func(room *Room, msg message.CommandMsg) error {
			op := room.IsOp(msg.From())
			room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix: "/me",
		Handler: func(room *Room, msg message.CommandMsg) error {
			me := strings.TrimLeft(msg.Body(), "/me")
			if me == "" {
				me = "is at a loss for words."
			} else {
				me = me[1:]
			}

			room.Send(message.NewEmoteMsg(me, msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix: "/exit",
		Help:   "Exit the chat.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			msg.From().Close()
			return nil
		},
	})
	c.Alias("/exit", "/quit")

	c.Add(Command{
		Prefix:     "/nick",
		PrefixHelp: "NAME",
		Help:       "Rename yourself.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			args := msg.Args()
			if len(args) != 1 {
				return ErrMissingArg
			}
			u := msg.From()

			member, ok := room.MemberByID(u.ID())
			if !ok {
				return errors.New("failed to find member")
			}

			oldID := member.ID()
			newID := sanitize.Name(args[0])
			if newID == oldID {
				return errors.New("new name is the same as the original")
			}
			member.SetID(newID)
			err := room.Rename(oldID, member)
			if err != nil {
				member.SetID(oldID)
				return err
			}
			return nil
		},
	})

	c.Add(Command{
		Prefix: "/names",
		Help:   "List users who are connected.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			theme := msg.From().Config().Theme

			colorize := func(u *message.User) string {
				return theme.ColorName(u)
			}

			if theme == nil {
				colorize = func(u *message.User) string {
					return u.Name()
				}
			}

			names := room.Members.ListPrefix("")
			sort.Slice(names, func(i, j int) bool { return names[i].Key() < names[j].Key() })
			colNames := make([]string, len(names))
			for i, uname := range names {
				colNames[i] = colorize(uname.Value().(*Member).User)
			}

			body := fmt.Sprintf("%d connected: %s", len(colNames), strings.Join(colNames, ", "))
			room.Send(message.NewSystemMsg(body, msg.From()))
			return nil
		},
	})
	c.Alias("/names", "/list")

	c.Add(Command{
		Prefix:     "/theme",
		PrefixHelp: "[colors|...]",
		Help:       "Set your color theme.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			user := msg.From()
			args := msg.Args()
			cfg := user.Config()
			if len(args) == 0 {
				theme := "plain"
				if cfg.Theme != nil {
					theme = cfg.Theme.ID()
				}
				var output strings.Builder
				fmt.Fprintf(&output, "Current theme: %s%s", theme, message.Newline)
				fmt.Fprintf(&output, "   Themes available: ")

				for i, t := range message.Themes {
					output.WriteString(t.ID())
					if i < len(message.Themes)-1 {
						output.WriteString(", ")
					}
				}
				room.Send(message.NewSystemMsg(output.String(), user))
				return nil
			}

			id := args[0]
			for _, t := range message.Themes {
				if t.ID() == id {
					cfg.Theme = &t
					user.SetConfig(cfg)
					body := fmt.Sprintf("Set theme: %s", id)
					room.Send(message.NewSystemMsg(body, user))
					return nil
				}
			}
			return errors.New("theme not found")
		},
	})

	c.Add(Command{
		Prefix: "/quiet",
		Help:   "Silence room announcements.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			u := msg.From()
			cfg := u.Config()
			cfg.Quiet = !cfg.Quiet
			u.SetConfig(cfg)

			var body string
			if cfg.Quiet {
				body = "Quiet mode is toggled ON"
			} else {
				body = "Quiet mode is toggled OFF"
			}
			room.Send(message.NewSystemMsg(body, u))
			return nil
		},
	})

	c.Add(Command{
		Prefix:     "/slap",
		PrefixHelp: "NAME",
		Handler: func(room *Room, msg message.CommandMsg) error {
			var me string
			args := msg.Args()
			if len(args) == 0 {
				me = "slaps themselves around a bit with a large trout."
			} else {
				me = fmt.Sprintf("slaps %s around a bit with a large trout.", strings.Join(args, " "))
			}

			room.Send(message.NewEmoteMsg(me, msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix: "/shrug",
		Handler: func(room *Room, msg message.CommandMsg) error {
			room.Send(message.NewEmoteMsg(`¯\_(ツ)_/¯`, msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix:     "/timestamp",
		PrefixHelp: "[time|datetime]",
		Help:       "Prefix messages with a timestamp. You can also provide the UTC offset: /timestamp time +5h45m",
		Handler: func(room *Room, msg message.CommandMsg) error {
			u := msg.From()
			cfg := u.Config()

			args := msg.Args()
			mode := ""
			if len(args) >= 1 {
				mode = args[0]
			}
			if len(args) >= 2 {
				// FIXME: This is an annoying format to demand from users, but
				// hopefully we can make it a non-primary flow if we add GeoIP
				// someday.
				offset, err := time.ParseDuration(args[1])
				if err != nil {
					return err
				}
				cfg.Timezone = time.FixedZone("", int(offset.Seconds()))
			}

			switch mode {
			case "time":
				cfg.Timeformat = &timeformatTime
			case "datetime":
				cfg.Timeformat = &timeformatDatetime
			case "":
				// Toggle
				if cfg.Timeformat != nil {
					cfg.Timeformat = nil
				} else {
					cfg.Timeformat = &timeformatTime
				}
			case "off":
				cfg.Timeformat = nil
			default:
				return errors.New("timestamp value must be one of: time, datetime, off")
			}

			u.SetConfig(cfg)

			var body string
			if cfg.Timeformat != nil {
				if cfg.Timezone != nil {
					tzname := time.Now().In(cfg.Timezone).Format("MST")
					body = fmt.Sprintf("Timestamp is toggled ON, timezone is %q", tzname)
				} else {
					body = "Timestamp is toggled ON, timezone is UTC"
				}
			} else {
				body = "Timestamp is toggled OFF"
			}
			room.Send(message.NewSystemMsg(body, u))
			return nil
		},
	})

	c.Add(Command{
		Prefix:     "/ignore",
		PrefixHelp: "[USER]",
		Help:       "Hide messages from USER, /unignore USER to stop hiding.",
		Handler: func(room *Room, msg message.CommandMsg) error {
			id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/ignore"))
			if id == "" {
				// Print ignored names, if any.
				var names []string
				msg.From().Ignored.Each(func(_ string, item set.Item) error {
					names = append(names, item.Key())
					return nil
				})

				var systemMsg string
				if len(names) == 0 {
					systemMsg = "0 users ignored."
				} else {
					systemMsg = fmt.Sprintf("%d ignored: %s", len(names), strings.Join(names, ", "))
				}

				room.Send(message.NewSystemMsg(systemMsg, msg.From()))
				return nil
			}

			if id == msg.From().ID() {
				return errors.New("cannot ignore self")
			}
			target, ok := room.MemberByID(id)
			if !ok {
				return fmt.Errorf("user not found: %s", id)
			}

			err := msg.From().Ignored.Add(set.Itemize(id, target))
			if err == set.ErrCollision {
				return fmt.Errorf("user already ignored: %s", id)
			} else if err != nil {
				return err
			}

			room.Send(message.NewSystemMsg(fmt.Sprintf("Ignoring: %s", target.Name()), msg.From()))
			return nil
		},
	})

	c.Add(Command{
		Prefix:     "/unignore",
		PrefixHelp: "USER",
		Handler: func(room *Room, msg message.CommandMsg) error {
			id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/unignore"))
			if id == "" {
				return errors.New("must specify user")
			}

			if err := msg.From().Ignored.Remove(id); err != nil {
				return err
			}

			room.Send(message.NewSystemMsg(fmt.Sprintf("No longer ignoring: %s", id), msg.From()))
			return nil
		},
	})
}
<|endoftext|>