<commit_before>package main

import (
	"fmt"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
	"text/tabwriter"

	"github.com/michaelsauter/crane/print"
)

type Container struct {
	Id         string
	Name       string `json:"name" yaml:"name"`
	Dockerfile string `json:"dockerfile" yaml:"dockerfile"`
	Image      string `json:"image" yaml:"image"`
	Manual     bool   `json:"manual" yaml:"manual"`
	Run        RunParameters
}

type RunParameters struct {
	RawAddHost        []string    `json:"add-host" yaml:"add-host"`
	RawOther          []string    `json:"other" yaml:"other"`
	Cidfile           string      `json:"cidfile" yaml:"cidfile"`
	CpuShares         int         `json:"cpu-shares" yaml:"cpu-shares"`
	Detach            bool        `json:"detach" yaml:"detach"`
	Dns               []string    `json:"dns" yaml:"dns"`
	Entrypoint        string      `json:"entrypoint" yaml:"entrypoint"`
	Env               []string    `json:"env" yaml:"env"`
	Expose            []string    `json:"expose" yaml:"expose"`
	Host              string      `json:"host" yaml:"host"`
	Interactive       bool        `json:"interactive" yaml:"interactive"`
	Link              []string    `json:"link" yaml:"link"`
	LxcConf           []string    `json:"lxc-conf" yaml:"lxc-conf"`
	MappedVolumesFrom []string    `json:"mapped-volumes-from" yaml:"mapped-volumes-from"`
	Memory            string      `json:"memory" yaml:"memory"`
	RawNet            string      `json:"net" yaml:"net"`
	Privileged        bool        `json:"privileged" yaml:"privileged"`
	Publish           []string    `json:"publish" yaml:"publish"`
	PublishAll        bool        `json:"publish-all" yaml:"publish-all"`
	Rm                bool        `json:"rm" yaml:"rm"`
	Tty               bool        `json:"tty" yaml:"tty"`
	User              string      `json:"user" yaml:"user"`
	Volume            []string    `json:"volume" yaml:"volume"`
	VolumesFrom       []string    `json:"volumes-from" yaml:"volumes-from"`
	Workdir           string      `json:"workdir" yaml:"workdir"`
	Command           interface{} `json:"cmd" yaml:"cmd"`
}

func (r *RunParameters) AddHost() []string {
	var addHost []string
	for _, rawAddHost := range r.RawAddHost {
		addHost = append(addHost, os.ExpandEnv(rawAddHost))
	}
	return addHost
}

func (r *RunParameters) Other() []string {
	var other []string
	for _, rawOther := range r.RawOther {
		other = append(other, os.ExpandEnv(rawOther))
	}
	return other
}
func (r *RunParameters) Net() string {
	// Default to bridge
	if len(r.RawNet) == 0 {
		return "bridge"
	} else {
		return os.ExpandEnv(r.RawNet)
	}
}

func (container *Container) getId() (id string, err error) {
	if len(container.Id) > 0 {
		id = container.Id
	} else {
		// Inspect container, extracting the Id.
		// This will return gibberish if no container is found.
		args := []string{"inspect", "--format={{.Id}}|{{.ID}}", container.Name}
		output, outErr := commandOutput("docker", args)
		if err == nil {
			ids := strings.Split(output, "|")
			for _, possible_id := range ids {
				if possible_id != "<no value>" {
					id = possible_id
					container.Id = possible_id
					return
				}
			}
		} else {
			err = outErr
		}
	}
	return
}

func (container *Container) exists() bool {
	// `ps -a` returns all existant containers
	id, err := container.getId()
	if err != nil || len(id) == 0 {
		return false
	}
	dockerCmd := []string{"docker", "ps", "--quiet", "--all", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", id}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) running() bool {
	// `ps` returns all running containers
	id, err := container.getId()
	if err != nil || len(id) == 0 {
		return false
	}
	dockerCmd := []string{"docker", "ps", "--quiet", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", id}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) imageExists() bool {
	dockerCmd := []string{"docker", "images", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", container.Image}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) status(w *tabwriter.Writer) {
	args := []string{"inspect", "--format={{.State.Running}}\t{{.Id}}\t{{if .NetworkSettings.IPAddress}}{{.NetworkSettings.IPAddress}}{{else}}-{{end}}\t{{range $k,$v := $.NetworkSettings.Ports}}{{$k}},{{end}}", container.Name}
	output, err := commandOutput("docker", args)
	if err != nil {
		fmt.Fprintf(w, "%s\tError:%v\t%v\n", container.Name, err, output)
		return
	}
	fmt.Fprintf(w, "%s\t%s\n", container.Name, output)
}

// Pull image for container
func (container *Container) pullImage() {
	fmt.Printf("Pulling image %s ... ", container.Image)
	args := []string{"pull", container.Image}
	executeCommand("docker", args)
}

// Build image for container
func (container *Container) buildImage() {
	fmt.Printf("Building image %s ... ", container.Image)
	args := []string{"build", "--rm", "--tag=" + container.Image, os.ExpandEnv(container.Dockerfile)}
	executeCommand("docker", args)
}

func (container Container) provision(force bool) {
	if force || !container.imageExists() {
		if len(container.Dockerfile) > 0 {
			container.buildImage()
		} else {
			container.pullImage()
		}
	} else {
		print.Notice("Image %s does already exist. Use --force to recreate.\n", container.Image)
	}
}

func (container Container) pull(force bool) {
	if force || !container.imageExists() {
		container.pullImage()
	} else {
		print.Notice("Image %s does already exist. Use --force to re-pull.\n", container.Image)
	}
}

// Run or start container
func (container Container) runOrStart() {
	if container.exists() {
		container.start()
	} else {
		container.run()
	}
}

// Run container
func (container Container) run() {
	if !isManualTargetting() && container.Manual {
		return
	}

	if container.exists() {
		print.Notice("Container %s does already exist. Use --force to recreate.\n", container.Name)
		if !container.running() {
			container.start()
		}
	} else {
		fmt.Printf("Running container %s ... ", container.Name)
		// Assemble command arguments
		args := []string{"run"}
		// AddHost
		for _, addHost := range container.Run.AddHost() {
			args = append(args, "--add-host", addHost)
		}
		// Net
		if container.Run.Net() != "bridge" {
			args = append(args, "--net", container.Run.Net())
		}
		// Other
		for _, other := range container.Run.Other() {
			args = append(args, other)
		}

		// Cidfile
		if len(container.Run.Cidfile) > 0 {
			args = append(args, "--cidfile", os.ExpandEnv(container.Run.Cidfile))
		}
		// CPU shares
		if container.Run.CpuShares > 0 {
			args = append(args, "--cpu-shares", os.ExpandEnv(strconv.Itoa(container.Run.CpuShares)))
		}
		// Detach
		if container.Run.Detach {
			args = append(args, "--detach")
		}
		// Dns
		for _, dns := range container.Run.Dns {
			args = append(args, "--dns", os.ExpandEnv(dns))
		}
		// Entrypoint
		if len(container.Run.Entrypoint) > 0 {
			args = append(args, "--entrypoint", os.ExpandEnv(container.Run.Entrypoint))
		}
		// Env
		for _, env := range container.Run.Env {
			args = append(args, "--env", os.ExpandEnv(env))
		}
		// Expose
		for _, expose := range container.Run.Expose {
			args = append(args, "--expose", os.ExpandEnv(expose))
		}
		// Host
		if len(container.Run.Host) > 0 {
			args = append(args, "--hostname", os.ExpandEnv(container.Run.Host))
		}
		// Interactive
		if container.Run.Interactive {
			args = append(args, "--interactive")
		}
		// Link
		for _, link := range container.Run.Link {
			args = append(args, "--link", link)
		}
		// LxcConf
		for _, lxcConf := range container.Run.LxcConf {
			args = append(args, "--lxc-conf", os.ExpandEnv(lxcConf))
		}
		// MappedVolumesFrom
		for _, mappedVolumesFrom := range container.Run.MappedVolumesFrom {
			mappedVolumesFrom = os.ExpandEnv(mappedVolumesFrom)
			x := strings.Split(mappedVolumesFrom, ":")
			from, volume, dest := x[0], x[1], x[2]
			src_volume_dir := getSourceForVolume(from, volume)
			vol_map := strings.Join([]string{src_volume_dir, dest}, ":")
			args = append(args, "--volume", vol_map)
		}
		// Memory
		if len(container.Run.Memory) > 0 {
			args = append(args, "--memory", os.ExpandEnv(container.Run.Memory))
		}
		// Privileged
		if container.Run.Privileged {
			args = append(args, "--privileged")
		}
		// Publish
		for _, port := range container.Run.Publish {
			args = append(args, "--publish", os.ExpandEnv(port))
		}
		// PublishAll
		if container.Run.PublishAll {
			args = append(args, "--publish-all")
		}
		// Rm
		if container.Run.Rm {
			args = append(args, "--rm")
		}
		// Tty
		if container.Run.Tty {
			args = append(args, "--tty")
		}
		// User
		if len(container.Run.User) > 0 {
			args = append(args, "--user", os.ExpandEnv(container.Run.User))
		}
		// Volumes
		for _, volume := range container.Run.Volume {
			paths := strings.Split(os.ExpandEnv(volume), ":")
			if !path.IsAbs(paths[0]) {
				cwd, _ := os.Getwd()
				paths[0] = cwd + "/" + paths[0]
			}
			args = append(args, "--volume", strings.Join(paths, ":"))
		}
		// VolumesFrom
		for _, volumeFrom := range container.Run.VolumesFrom {
			args = append(args, "--volumes-from", os.ExpandEnv(volumeFrom))
		}
		// Workdir
		if len(container.Run.Workdir) > 0 {
			args = append(args, "--workdir", os.ExpandEnv(container.Run.Workdir))
		}

		// Name
		args = append(args, "--name", container.Name)
		// Image
		args = append(args, container.Image)
		// Command
		if container.Run.Command != nil {
			switch cmd := container.Run.Command.(type) {
			case string:
				if len(cmd) > 0 {
					args = append(args, cmd)
				}
			case []interface{}:
				cmds := make([]string, len(cmd))
				for i, v := range cmd {
					cmds[i] = v.(string)
				}
				args = append(args, cmds...)
			default:
				print.Error("cmd is of unknown type!")
			}
		}
		// Execute command
		executeCommand("docker", args)
	}
}

// Start container
func (container Container) start() {
	if !isManualTargetting() && container.Manual {
		return
	}

	if container.exists() {
		if !container.running() {
			fmt.Printf("Starting container %s ... ", container.Name)
			args := []string{"start", container.Name}
			executeCommand("docker", args)
		}
	} else {
		print.Error("Container %s does not exist.\n", container.Name)
	}
}

// Kill container
func (container Container) kill() {
	if container.running() {
		fmt.Printf("Killing container %s ... ", container.Name)
		args := []string{"kill", container.Name}
		executeCommand("docker", args)
	}
}

// Stop container
func (container Container) stop() {
	if container.running() {
		fmt.Printf("Stopping container %s ... ", container.Name)
		args := []string{"stop", container.Name}
		executeCommand("docker", args)
	}
}

// Remove container
func (container Container) rm() {
	if container.exists() {
		if container.running() {
			print.Error("Container %s is running and cannot be removed.\n", container.Name)
		} else {
			fmt.Printf("Removing container %s ... ", container.Name)
			args := []string{"rm", container.Name}
			executeCommand("docker", args)
		}
	}
}

func getSourceForVolume(from, volume string) string {
	args := []string{"inspect", "--format={{.Volumes}}", from}
	output, err := commandOutput("docker", args)
	if err != nil {
		panic(fmt.Sprintf("Cannot getSourceForVolume %v %v", from, volume))
	}
	// output looks like:
	// map[/scirev-admin:/home/core/scirev-admin /var/log/nginx/pricingconsole:/home/core/pc_logs /var/log/pricingconsole:/home/core/pc_logs]
	inner_part_regex := regexp.MustCompile("map[[](.*)[]]$")
	inner := inner_part_regex.FindStringSubmatch(output)[1]
	for _, vol_map := range strings.Split(inner, " ") {
		x := strings.Split(vol_map, ":")
		container_path, host_path := x[0], x[1]
		if container_path == volume {
			return host_path
		}
	}
	panic(fmt.Sprintf("getSourceForVolume cannot find volume %v for container %v", volume, from))
}
<commit_msg>container.go: rename run's 'host' config to be hostname (as in docker cmd)<commit_after>package main

import (
	"fmt"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
	"text/tabwriter"

	"github.com/michaelsauter/crane/print"
)

type Container struct {
	Id         string
	Name       string `json:"name" yaml:"name"`
	Dockerfile string `json:"dockerfile" yaml:"dockerfile"`
	Image      string `json:"image" yaml:"image"`
	Manual     bool   `json:"manual" yaml:"manual"`
	Run        RunParameters
}

type RunParameters struct {
	RawAddHost        []string    `json:"add-host" yaml:"add-host"`
	RawOther          []string    `json:"other" yaml:"other"`
	Cidfile           string      `json:"cidfile" yaml:"cidfile"`
	CpuShares         int         `json:"cpu-shares" yaml:"cpu-shares"`
	Detach            bool        `json:"detach" yaml:"detach"`
	Dns               []string    `json:"dns" yaml:"dns"`
	Entrypoint        string      `json:"entrypoint" yaml:"entrypoint"`
	Env               []string    `json:"env" yaml:"env"`
	Expose            []string    `json:"expose" yaml:"expose"`
	Hostname          string      `json:"hostname" yaml:"hostname"`
	Interactive       bool        `json:"interactive" yaml:"interactive"`
	Link              []string    `json:"link" yaml:"link"`
	LxcConf           []string    `json:"lxc-conf" yaml:"lxc-conf"`
	MappedVolumesFrom []string    `json:"mapped-volumes-from" yaml:"mapped-volumes-from"`
	Memory            string      `json:"memory" yaml:"memory"`
	RawNet            string      `json:"net" yaml:"net"`
	Privileged        bool        `json:"privileged" yaml:"privileged"`
	Publish           []string    `json:"publish" yaml:"publish"`
	PublishAll        bool        `json:"publish-all" yaml:"publish-all"`
	Rm                bool        `json:"rm" yaml:"rm"`
	Tty               bool        `json:"tty" yaml:"tty"`
	User              string      `json:"user" yaml:"user"`
	Volume            []string    `json:"volume" yaml:"volume"`
	VolumesFrom       []string    `json:"volumes-from" yaml:"volumes-from"`
	Workdir           string      `json:"workdir" yaml:"workdir"`
	Command           interface{} `json:"cmd" yaml:"cmd"`
}

func (r *RunParameters) AddHost() []string {
	var addHost []string
	for _, rawAddHost := range r.RawAddHost {
		addHost = append(addHost, os.ExpandEnv(rawAddHost))
	}
	return addHost
}

func (r *RunParameters) Other() []string {
	var other []string
	for _, rawOther := range r.RawOther {
		other = append(other, os.ExpandEnv(rawOther))
	}
	return other
}
func (r *RunParameters) Net() string {
	// Default to bridge
	if len(r.RawNet) == 0 {
		return "bridge"
	} else {
		return os.ExpandEnv(r.RawNet)
	}
}

func (container *Container) getId() (id string, err error) {
	if len(container.Id) > 0 {
		id = container.Id
	} else {
		// Inspect container, extracting the Id.
		// This will return gibberish if no container is found.
		args := []string{"inspect", "--format={{.Id}}|{{.ID}}", container.Name}
		output, outErr := commandOutput("docker", args)
		if err == nil {
			ids := strings.Split(output, "|")
			for _, possible_id := range ids {
				if possible_id != "<no value>" {
					id = possible_id
					container.Id = possible_id
					return
				}
			}
		} else {
			err = outErr
		}
	}
	return
}

func (container *Container) exists() bool {
	// `ps -a` returns all existant containers
	id, err := container.getId()
	if err != nil || len(id) == 0 {
		return false
	}
	dockerCmd := []string{"docker", "ps", "--quiet", "--all", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", id}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) running() bool {
	// `ps` returns all running containers
	id, err := container.getId()
	if err != nil || len(id) == 0 {
		return false
	}
	dockerCmd := []string{"docker", "ps", "--quiet", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", id}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) imageExists() bool {
	dockerCmd := []string{"docker", "images", "--no-trunc"}
	grepCmd := []string{"grep", "-wF", container.Image}
	output, err := pipedCommandOutput(dockerCmd, grepCmd)
	if err != nil {
		return false
	}
	result := string(output)
	if len(result) > 0 {
		return true
	} else {
		return false
	}
}

func (container *Container) status(w *tabwriter.Writer) {
	args := []string{"inspect", "--format={{.State.Running}}\t{{.Id}}\t{{if .NetworkSettings.IPAddress}}{{.NetworkSettings.IPAddress}}{{else}}-{{end}}\t{{range $k,$v := $.NetworkSettings.Ports}}{{$k}},{{end}}", container.Name}
	output, err := commandOutput("docker", args)
	if err != nil {
		fmt.Fprintf(w, "%s\tError:%v\t%v\n", container.Name, err, output)
		return
	}
	fmt.Fprintf(w, "%s\t%s\n", container.Name, output)
}

// Pull image for container
func (container *Container) pullImage() {
	fmt.Printf("Pulling image %s ... ", container.Image)
	args := []string{"pull", container.Image}
	executeCommand("docker", args)
}

// Build image for container
func (container *Container) buildImage() {
	fmt.Printf("Building image %s ... ", container.Image)
	args := []string{"build", "--rm", "--tag=" + container.Image, os.ExpandEnv(container.Dockerfile)}
	executeCommand("docker", args)
}

func (container Container) provision(force bool) {
	if force || !container.imageExists() {
		if len(container.Dockerfile) > 0 {
			container.buildImage()
		} else {
			container.pullImage()
		}
	} else {
		print.Notice("Image %s does already exist. Use --force to recreate.\n", container.Image)
	}
}

func (container Container) pull(force bool) {
	if force || !container.imageExists() {
		container.pullImage()
	} else {
		print.Notice("Image %s does already exist. Use --force to re-pull.\n", container.Image)
	}
}

// Run or start container
func (container Container) runOrStart() {
	if container.exists() {
		container.start()
	} else {
		container.run()
	}
}

// Run container
func (container Container) run() {
	if !isManualTargetting() && container.Manual {
		return
	}

	if container.exists() {
		print.Notice("Container %s does already exist. Use --force to recreate.\n", container.Name)
		if !container.running() {
			container.start()
		}
	} else {
		fmt.Printf("Running container %s ... ", container.Name)
		// Assemble command arguments
		args := []string{"run"}
		// AddHost
		for _, addHost := range container.Run.AddHost() {
			args = append(args, "--add-host", addHost)
		}
		// Net
		if container.Run.Net() != "bridge" {
			args = append(args, "--net", container.Run.Net())
		}
		// Other
		for _, other := range container.Run.Other() {
			args = append(args, other)
		}

		// Cidfile
		if len(container.Run.Cidfile) > 0 {
			args = append(args, "--cidfile", os.ExpandEnv(container.Run.Cidfile))
		}
		// CPU shares
		if container.Run.CpuShares > 0 {
			args = append(args, "--cpu-shares", os.ExpandEnv(strconv.Itoa(container.Run.CpuShares)))
		}
		// Detach
		if container.Run.Detach {
			args = append(args, "--detach")
		}
		// Dns
		for _, dns := range container.Run.Dns {
			args = append(args, "--dns", os.ExpandEnv(dns))
		}
		// Entrypoint
		if len(container.Run.Entrypoint) > 0 {
			args = append(args, "--entrypoint", os.ExpandEnv(container.Run.Entrypoint))
		}
		// Env
		for _, env := range container.Run.Env {
			args = append(args, "--env", os.ExpandEnv(env))
		}
		// Expose
		for _, expose := range container.Run.Expose {
			args = append(args, "--expose", os.ExpandEnv(expose))
		}
		// Hostname
		if len(container.Run.Hostname) > 0 {
			args = append(args, "--hostname", os.ExpandEnv(container.Run.Hostname))
		}
		// Interactive
		if container.Run.Interactive {
			args = append(args, "--interactive")
		}
		// Link
		for _, link := range container.Run.Link {
			args = append(args, "--link", link)
		}
		// LxcConf
		for _, lxcConf := range container.Run.LxcConf {
			args = append(args, "--lxc-conf", os.ExpandEnv(lxcConf))
		}
		// MappedVolumesFrom
		for _, mappedVolumesFrom := range container.Run.MappedVolumesFrom {
			mappedVolumesFrom = os.ExpandEnv(mappedVolumesFrom)
			x := strings.Split(mappedVolumesFrom, ":")
			from, volume, dest := x[0], x[1], x[2]
			src_volume_dir := getSourceForVolume(from, volume)
			vol_map := strings.Join([]string{src_volume_dir, dest}, ":")
			args = append(args, "--volume", vol_map)
		}
		// Memory
		if len(container.Run.Memory) > 0 {
			args = append(args, "--memory", os.ExpandEnv(container.Run.Memory))
		}
		// Privileged
		if container.Run.Privileged {
			args = append(args, "--privileged")
		}
		// Publish
		for _, port := range container.Run.Publish {
			args = append(args, "--publish", os.ExpandEnv(port))
		}
		// PublishAll
		if container.Run.PublishAll {
			args = append(args, "--publish-all")
		}
		// Rm
		if container.Run.Rm {
			args = append(args, "--rm")
		}
		// Tty
		if container.Run.Tty {
			args = append(args, "--tty")
		}
		// User
		if len(container.Run.User) > 0 {
			args = append(args, "--user", os.ExpandEnv(container.Run.User))
		}
		// Volumes
		for _, volume := range container.Run.Volume {
			paths := strings.Split(os.ExpandEnv(volume), ":")
			if !path.IsAbs(paths[0]) {
				cwd, _ := os.Getwd()
				paths[0] = cwd + "/" + paths[0]
			}
			args = append(args, "--volume", strings.Join(paths, ":"))
		}
		// VolumesFrom
		for _, volumeFrom := range container.Run.VolumesFrom {
			args = append(args, "--volumes-from", os.ExpandEnv(volumeFrom))
		}
		// Workdir
		if len(container.Run.Workdir) > 0 {
			args = append(args, "--workdir", os.ExpandEnv(container.Run.Workdir))
		}

		// Name
		args = append(args, "--name", container.Name)
		// Image
		args = append(args, container.Image)
		// Command
		if container.Run.Command != nil {
			switch cmd := container.Run.Command.(type) {
			case string:
				if len(cmd) > 0 {
					args = append(args, cmd)
				}
			case []interface{}:
				cmds := make([]string, len(cmd))
				for i, v := range cmd {
					cmds[i] = v.(string)
				}
				args = append(args, cmds...)
			default:
				print.Error("cmd is of unknown type!")
			}
		}
		// Execute command
		executeCommand("docker", args)
	}
}

// Start container
func (container Container) start() {
	if !isManualTargetting() && container.Manual {
		return
	}

	if container.exists() {
		if !container.running() {
			fmt.Printf("Starting container %s ... ", container.Name)
			args := []string{"start", container.Name}
			executeCommand("docker", args)
		}
	} else {
		print.Error("Container %s does not exist.\n", container.Name)
	}
}

// Kill container
func (container Container) kill() {
	if container.running() {
		fmt.Printf("Killing container %s ... ", container.Name)
		args := []string{"kill", container.Name}
		executeCommand("docker", args)
	}
}

// Stop container
func (container Container) stop() {
	if container.running() {
		fmt.Printf("Stopping container %s ... ", container.Name)
		args := []string{"stop", container.Name}
		executeCommand("docker", args)
	}
}

// Remove container
func (container Container) rm() {
	if container.exists() {
		if container.running() {
			print.Error("Container %s is running and cannot be removed.\n", container.Name)
		} else {
			fmt.Printf("Removing container %s ... ", container.Name)
			args := []string{"rm", container.Name}
			executeCommand("docker", args)
		}
	}
}

func getSourceForVolume(from, volume string) string {
	args := []string{"inspect", "--format={{.Volumes}}", from}
	output, err := commandOutput("docker", args)
	if err != nil {
		panic(fmt.Sprintf("Cannot getSourceForVolume %v %v", from, volume))
	}
	// output looks like:
	// map[/scirev-admin:/home/core/scirev-admin /var/log/nginx/pricingconsole:/home/core/pc_logs /var/log/pricingconsole:/home/core/pc_logs]
	inner_part_regex := regexp.MustCompile("map[[](.*)[]]$")
	inner := inner_part_regex.FindStringSubmatch(output)[1]
	for _, vol_map := range strings.Split(inner, " ") {
		x := strings.Split(vol_map, ":")
		container_path, host_path := x[0], x[1]
		if container_path == volume {
			return host_path
		}
	}
	panic(fmt.Sprintf("getSourceForVolume cannot find volume %v for container %v", volume, from))
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/dghubble/go-twitter/twitter"
	"github.com/dghubble/oauth1"
	"strings"
	"time"
)

var (
	twitterBot *TwitterBot
)

// TwitterBot ...
type TwitterBot struct {
	ID      string
	ImgPath string
	Client  *twitter.Client
	Follows map[string]string
}

// NewTwitterBot ...
func NewTwitterBot(cfg *TwitterConfig) *TwitterBot {
	config := oauth1.NewConfig(cfg.ConsumerKey, cfg.ConsumerSecret)
	token := oauth1.NewToken(cfg.AccessToken, cfg.AccessSecret)
	httpClient := config.Client(oauth1.NoContext, token)
	client := twitter.NewClient(httpClient)
	bot := &TwitterBot{
		ID:      cfg.IDSelf,
		ImgPath: cfg.ImgPath,
		Client:  client,
		Follows: map[string]string{
			"KanColle_STAFF": "294025417",
			"maesanpicture":  "2381595966",
			"komatan":        "96604067",
			"Strangestone":   "93332575",
		},
	}
	return bot
}

func logAllTrack(msg interface{}) {
	logger.Debug(msg)
}

func trackSendPics(medias []twitter.MediaEntity) {
	for _, media := range medias {
		switch media.Type {
		case "photo":
			fileName, err := downloadFile(media.MediaURLHttps, qqBot.Config.ImgPath)
			if err != nil {
				continue
			}
			qqBot.SendGroupMsg(QQImage{fileName}.String())
		}
	}
}

func hasHashTags(s string, tags []twitter.HashtagEntity) bool {
	for _, tag := range tags {
		if s == tag.Text {
			return true
		}
	}
	return false
}

func proceedTrack(tweet *twitter.Tweet) {
	if tweet.RetweetedStatus != nil {
		logger.Debugf("ignore retweet (%s):{%s}", tweet.User.Name, tweet.Text)
		return
	}
	switch tweet.User.IDStr {
	case twitterBot.Follows["KanColle_STAFF"]:
		medias := getMedias(tweet)
		trackSendPics(medias)
		logger.Infof("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	case twitterBot.Follows["maesanpicture"]:
		medias := getMedias(tweet)
		if !hasHashTags("毎日五月雨", tweet.Entities.Hashtags) || len(medias) == 0 {
			logger.Debugf("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
			return
		}
		qqBot.SendGroupMsg(tweet.Text)
		trackSendPics(medias)
		logger.Infof("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	case twitterBot.Follows["komatan"]:
		medias := getMedias(tweet)
		trackSendPics(medias)
		logger.Infof("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	case twitterBot.Follows["Strangestone"]:
		medias := getMedias(tweet)
		if !strings.HasPrefix(tweet.Text, "月曜日のたわわ") || len(medias) == 0 {
			logger.Debugf("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
			return
		}
		qqBot.SendGroupMsg(tweet.Text)
		trackSendPics(medias)
		logger.Infof("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	default:
		logger.Debugf("(%s):{%s}", tweet.User.Name, tweet.Text)
	}
}

func getMedias(tweet *twitter.Tweet) []twitter.MediaEntity {
	ee := tweet.ExtendedEntities
	if ee != nil {
		return ee.Media
	}
	return tweet.Entities.Media
}

func selfProceedPics(medias []twitter.MediaEntity, action int) {
	for _, media := range medias {
		switch media.Type {
		case "photo":
			switch action {
			case 1:
				downloadFile(media.MediaURLHttps, twitterBot.ImgPath)
			case -1:
				removeFile(media.MediaURLHttps, twitterBot.ImgPath)
			}
		}
	}
}

func eventSelf(event *twitter.Event) {
	switch event.Event {
	case "favorite":
		medias := getMedias(event.TargetObject)
		logger.Infof("favorite: [%s] %d medias", strings.Replace(event.TargetObject.Text, "\n", " ", -1), len(medias))
		go selfProceedPics(medias, 1)
	case "unfavorite":
		medias := getMedias(event.TargetObject)
		logger.Debugf("unfavorite: [%s] %d medias", strings.Replace(event.TargetObject.Text, "\n", " ", -1), len(medias))
		go selfProceedPics(medias, -1)
	default:
		logger.Debug(event.Event)
	}
}

func twitterTrack() {
	follows := []string{}
	for _, value := range twitterBot.Follows {
		follows = append(follows, value)
	}
	for i := 1; ; i++ {
		demux := twitter.NewSwitchDemux()
		demux.Tweet = proceedTrack
		filterParams := &twitter.StreamFilterParams{
			Follow: follows,
		}
		stream, err := twitterBot.Client.Streams.Filter(filterParams)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
		}
		demux.HandleChan(stream.Messages)
	}
}

func twitterSelf() {
	for i := 1; ; i++ {
		demux := twitter.NewSwitchDemux()
		demux.Event = eventSelf
		userParams := &twitter.StreamUserParams{
			With: twitterBot.ID,
		}
		stream, err := twitterBot.Client.Streams.User(userParams)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
		}
		demux.HandleChan(stream.Messages)
	}
}
<commit_msg>change func name<commit_after>package main

import (
	"github.com/dghubble/go-twitter/twitter"
	"github.com/dghubble/oauth1"
	"strings"
	"time"
)

var (
	twitterBot *TwitterBot
)

// TwitterBot ...
type TwitterBot struct {
	ID      string
	ImgPath string
	Client  *twitter.Client
	Follows map[string]string
}

// NewTwitterBot ...
func NewTwitterBot(cfg *TwitterConfig) *TwitterBot {
	config := oauth1.NewConfig(cfg.ConsumerKey, cfg.ConsumerSecret)
	token := oauth1.NewToken(cfg.AccessToken, cfg.AccessSecret)
	httpClient := config.Client(oauth1.NoContext, token)
	client := twitter.NewClient(httpClient)
	bot := &TwitterBot{
		ID:      cfg.IDSelf,
		ImgPath: cfg.ImgPath,
		Client:  client,
		Follows: map[string]string{
			"KanColle_STAFF": "294025417",
			"maesanpicture":  "2381595966",
			"komatan":        "96604067",
			"Strangestone":   "93332575",
		},
	}
	return bot
}

func logAllTrack(msg interface{}) {
	logger.Debug(msg)
}

func trackSendPics(medias []twitter.MediaEntity) {
	for _, media := range medias {
		switch media.Type {
		case "photo":
			fileName, err := downloadFile(media.MediaURLHttps, qqBot.Config.ImgPath)
			if err != nil {
				continue
			}
			qqBot.SendGroupMsg(QQImage{fileName}.String())
		}
	}
}

func hasHashTags(s string, tags []twitter.HashtagEntity) bool {
	for _, tag := range tags {
		if s == tag.Text {
			return true
		}
	}
	return false
}

func trackTweet(tweet *twitter.Tweet) {
	if tweet.RetweetedStatus != nil {
		logger.Debugf("ignore retweet (%s):{%s}", tweet.User.Name, tweet.Text)
		return
	}
	switch tweet.User.IDStr {
	case twitterBot.Follows["KanColle_STAFF"]:
		medias := getMedias(tweet)
		trackSendPics(medias)
		logger.Infof("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	case twitterBot.Follows["maesanpicture"]:
		medias := getMedias(tweet)
		if !hasHashTags("毎日五月雨", tweet.Entities.Hashtags) || len(medias) == 0 {
			logger.Debugf("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
			return
		}
		qqBot.SendGroupMsg(tweet.Text)
		trackSendPics(medias)
		logger.Infof("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	case twitterBot.Follows["komatan"]:
		medias := getMedias(tweet)
		trackSendPics(medias)
		logger.Infof("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	case twitterBot.Follows["Strangestone"]:
		medias := getMedias(tweet)
		if !strings.HasPrefix(tweet.Text, "月曜日のたわわ") || len(medias) == 0 {
			logger.Debugf("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
			return
		}
		qqBot.SendGroupMsg(tweet.Text)
		trackSendPics(medias)
		logger.Infof("%s: {%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	default:
		logger.Debugf("(%s):{%s}", tweet.User.Name, tweet.Text)
	}
}

func getMedias(tweet *twitter.Tweet) []twitter.MediaEntity {
	ee := tweet.ExtendedEntities
	if ee != nil {
		return ee.Media
	}
	return tweet.Entities.Media
}

func selfProceedPics(medias []twitter.MediaEntity, action int) {
	for _, media := range medias {
		switch media.Type {
		case "photo":
			switch action {
			case 1:
				downloadFile(media.MediaURLHttps, twitterBot.ImgPath)
			case -1:
				removeFile(media.MediaURLHttps, twitterBot.ImgPath)
			}
		}
	}
}

func selfEvent(event *twitter.Event) {
	switch event.Event {
	case "favorite":
		medias := getMedias(event.TargetObject)
		logger.Infof(
			"favorite: (%s):[%s] %d medias",
			event.TargetObject.User.Name,
			strings.Replace(event.TargetObject.Text, "\n", " ", -1),
			len(medias))
		go selfProceedPics(medias, 1)
	case "unfavorite":
		medias := getMedias(event.TargetObject)
		logger.Debugf(
			"unfavorite: (%s):[%s] %d medias",
			event.TargetObject.User.Name,
			strings.Replace(event.TargetObject.Text, "\n", " ", -1),
			len(medias))
		go selfProceedPics(medias, -1)
	default:
		logger.Debug(event.Event)
	}
}

func twitterTrack() {
	follows := []string{}
	for _, value := range twitterBot.Follows {
		follows = append(follows, value)
	}
	for i := 1; ; i++ {
		demux := twitter.NewSwitchDemux()
		demux.Tweet = trackTweet
		filterParams := &twitter.StreamFilterParams{
			Follow: follows,
		}
		stream, err := twitterBot.Client.Streams.Filter(filterParams)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
		}
		demux.HandleChan(stream.Messages)
	}
}

func twitterSelf() {
	for i := 1; ; i++ {
		demux := twitter.NewSwitchDemux()
		demux.Event = selfEvent
		userParams := &twitter.StreamUserParams{
			With: twitterBot.ID,
		}
		stream, err := twitterBot.Client.Streams.User(userParams)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
		}
		demux.HandleChan(stream.Messages)
	}
}
<|endoftext|>


<commit_before>package superast

import (
	"go/ast"
	"go/token"
	"log"
	"strconv"
	"strings"
)

const (
	Default = iota
	IfBody
	IfElse
	FuncBody
)

type AST struct {
	curID      int
	RootBlock  *block
	nodeStack  []ast.Node
	blockStack []*block
	fset       *token.FileSet
	pos        token.Pos
}

func NewAST(fset *token.FileSet) *AST {
	a := &AST{
		fset: fset,
	}
	a.RootBlock = &block{
		id:    a.newID(),
		Stmts: make([]stmt, 0),
	}
	a.pushBlock(a.RootBlock)
	return a
}

func (a *AST) newID() id {
	i := a.curID
	a.curID++
	return id{ID: i}
}

func (a *AST) newPos(p token.Pos) pos {
	position := a.fset.Position(p)
	return pos{Line: position.Line, Col: position.Column}
}

func (a *AST) nodePos(n ast.Node) pos {
	return a.newPos(n.Pos())
}

func (a *AST) curPos() pos {
	return a.newPos(a.pos)
}

func (a *AST) pushNode(node ast.Node) {
	a.nodeStack = append(a.nodeStack, node)
}

func (a *AST) curNode() ast.Node {
	return a.nodeStack[len(a.nodeStack)-1]
}

func (a *AST) popNode() {
	a.nodeStack = a.nodeStack[:len(a.nodeStack)-1]
}

func (a *AST) pushBlock(b *block) {
	a.blockStack = append(a.blockStack, b)
}

func (a *AST) curBlock() *block {
	return a.blockStack[len(a.blockStack)-1]
}

func (a *AST) addStmt(s stmt) {
	b := a.curBlock()
	b.Stmts = append(b.Stmts, s)
}

func (a *AST) popBlock() {
	a.blockStack = a.blockStack[:len(a.blockStack)-1]
}

func exprString(x ast.Expr) string {
	switch t := x.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.BasicLit:
		return t.Value
	case *ast.SelectorExpr:
		return exprString(t.X) + "." + t.Sel.Name
	case *ast.StarExpr:
		return exprString(t.X)
	}
	return ""
}

func exprValue(x ast.Expr) value {
	switch t := x.(type) {
	case *ast.BasicLit:
		switch t.Kind {
		case token.INT:
			i, _ := strconv.ParseInt(t.Value, 10, 0)
			return i
		case token.FLOAT:
			f, _ := strconv.ParseFloat(t.Value, 64)
			return f
		case token.CHAR:
			r, _, _, _ := strconv.UnquoteChar(t.Value, '\'')
			return r
		case token.STRING:
			s, _ := strconv.Unquote(t.Value)
			return s
		}
		return t.Value
	}
	return nil
}

func exprType(x ast.Expr) *dataType {
	if s := exprString(x); s != "" {
		return &dataType{
			Name: s,
		}
	}
	switch t := x.(type) {
	case *ast.ArrayType:
		return &dataType{
			Name:    "vector",
			SubType: exprType(t.Elt),
		}
	}
	return nil
}

type namedType struct {
	vName string
	dType *dataType
	node  ast.Node
}

func flattenNames(baseType ast.Expr, names []*ast.Ident) []namedType {
	t := exprType(baseType)
	if len(names) == 0 {
		return []namedType{
			{vName: "", dType: t},
		}
	}
	var types []namedType
	for _, n := range names {
		types = append(types, namedType{
			vName: n.Name,
			dType: t,
			node:  n,
		})
	}
	return types
}

func flattenFieldList(fieldList *ast.FieldList) []namedType {
	if fieldList == nil {
		return nil
	}
	var types []namedType
	for _, f := range fieldList.List {
		for _, t := range flattenNames(f.Type, f.Names) {
			types = append(types, t)
		}
	}
	return types
}

var basicLitName = map[token.Token]string{
	token.INT:    "int",
	token.FLOAT:  "double",
	token.CHAR:   "char",
	token.STRING: "string",
}

var zeroValues = map[string]value{
	"int":    new(int),
	"double": new(float64),
	"char":   new(rune),
	"string": new(string),
}

func (a *AST) parseExpr(expr ast.Expr) expr {
	switch x := expr.(type) {
	case *ast.Ident:
		return &identifier{
			id:    a.newID(),
			pos:   a.nodePos(x),
			Type:  "identifier",
			Value: x.Name,
		}
	case *ast.BasicLit:
		lType, _ := basicLitName[x.Kind]
		return &identifier{
			id:    a.newID(),
			pos:   a.nodePos(x),
			Type:  lType,
			Value: exprValue(x),
		}
	case *ast.UnaryExpr:
		return &unary{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: x.Op.String(),
			Expr: a.parseExpr(x.X),
		}
	case *ast.CallExpr:
		call := &funcCall{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: "function-call",
			Name: exprString(x.Fun),
		}
		for _, e := range x.Args {
			call.Args = append(call.Args, a.parseExpr(e))
		}
		return call
	case *ast.BinaryExpr:
		return &binary{
			id:    a.newID(),
			pos:   a.nodePos(x),
			Type:  x.Op.String(),
			Left:  a.parseExpr(x.X),
			Right: a.parseExpr(x.Y),
		}
	case *ast.ParenExpr:
		return a.parseExpr(x.X)
	default:
		log.Printf("Unknown expression: %#v", x)
	}
	return nil
}

func (a *AST) assignIdToDataType(dType *dataType) *dataType {
	if dType == nil {
		return nil
	}
	dTypeCopy := *dType
	dTypeCopy.id = a.newID()
	if dTypeCopy.SubType != nil {
		dTypeCopy.SubType = a.assignIdToDataType(dTypeCopy.SubType)
	}
	return &dTypeCopy
}

func (a *AST) Visit(node ast.Node) ast.Visitor {
	if node == nil {
		switch a.curNode().(type) {
		case *ast.BlockStmt:
			a.popBlock()
		}
		a.popNode()
		return nil
	}
	a.pos = node.Pos()
	log.Printf("%s%#v", strings.Repeat("  ", len(a.nodeStack)), node)
	switch x := node.(type) {
	case *ast.TypeSpec:
		n := ""
		if x.Name != nil {
			n = exprString(x.Name)
		}
		switch t := x.Type.(type) {
		case *ast.StructType:
			d := &structDecl{
				id:   a.newID(),
				pos:  a.curPos(),
				Type: "struct-declaration",
				Name: n,
			}
			for _, f := range flattenFieldList(t.Fields) {
				attr := varDecl{
					id:       a.newID(),
					pos:      a.curPos(),
					Type:     "variable-declaration",
					Name:     f.vName,
					DataType: a.assignIdToDataType(f.dType),
				}
				d.Attrs = append(d.Attrs, attr)
			}
			a.addStmt(d)
		}
		return nil
	case *ast.BasicLit:
		l := a.parseExpr(x)
		a.addStmt(l)
		return nil
	case *ast.UnaryExpr:
		u := a.parseExpr(x)
		a.addStmt(u)
		return nil
	case *ast.CallExpr:
		c := a.parseExpr(x)
		a.addStmt(c)
		return nil
	case *ast.FuncDecl:
		name := x.Name.Name
		var retType *dataType
		results := flattenFieldList(x.Type.Results)
		switch len(results) {
		case 1:
			retType = results[0].dType
		}
		d := &funcDecl{
			id:      a.newID(),
			pos:     a.nodePos(x),
			Type:    "function-declaration",
			Name:    name,
			RetType: a.assignIdToDataType(retType),
			Block: &block{
				id:    a.newID(),
				Stmts: make([]stmt, 0),
			},
		}
		for _, f := range flattenFieldList(x.Type.Params) {
			param := varDecl{
				id:       a.newID(),
				pos:      a.nodePos(f.node),
				Type:     "variable-declaration",
				Name:     f.vName,
				DataType: a.assignIdToDataType(f.dType),
			}
			d.Params = append(d.Params, param)
		}
		a.addStmt(d)
		a.pushBlock(d.Block)
	case *ast.DeclStmt:
		gd, _ := x.Decl.(*ast.GenDecl)
		for _, spec := range gd.Specs {
			s, ok := spec.(*ast.ValueSpec)
			if !ok {
				continue
			}
			for i, t := range flattenNames(s.Type, s.Names) {
				vType := t.dType.Name
				v, _ := zeroValues[vType]
				if s.Values != nil {
					v = exprValue(s.Values[i])
				}
				d := &varDecl{
					id:       a.newID(),
					pos:      a.curPos(),
					Type:     "variable-declaration",
					Name:     t.vName,
					DataType: a.assignIdToDataType(t.dType),
					Init: &identifier{
						id:    a.newID(),
						pos:   a.curPos(),
						Type:  vType,
						Value: v,
					},
				}
				a.addStmt(d)
			}
		}
		return nil
	case *ast.AssignStmt:
		for i, l := range x.Lhs {
			r := x.Rhs[i]
			var t string
			switch rx := r.(type) {
			case *ast.BasicLit:
				t, _ = basicLitName[rx.Kind]
			case *ast.CompositeLit:
				t = exprString(rx.Type)
			}
			var s stmt
			if x.Tok == token.DEFINE {
				s = &varDecl{
					id:   a.newID(),
					pos:  a.curPos(),
					Type: "variable-declaration",
					Name: exprString(l),
					DataType: &dataType{
						id:   a.newID(),
						Name: t,
					},
					Init: a.parseExpr(r),
				}
			} else {
				s = &binary{
					id:    a.newID(),
					pos:   a.curPos(),
					Type:  x.Tok.String(),
					Left:  a.parseExpr(l),
					Right: a.parseExpr(r),
				}
			}
			a.addStmt(s)
		}
		return nil
	case *ast.ReturnStmt:
		r := &retStmt{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: "return",
		}
		if len(x.Results) > 0 {
			r.Expr = a.parseExpr(x.Results[0])
		}
		a.addStmt(r)
		return nil
	case *ast.IfStmt:
		c := &conditional{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: "conditional",
			Cond: a.parseExpr(x.Cond),
			Then: &block{
				id:    a.newID(),
				Stmts: make([]stmt, 0),
			},
		}
		a.addStmt(c)
		if x.Else != nil {
			c.Else = &block{
				id:    a.newID(),
				Stmts: make([]stmt, 0),
			}
			a.pushBlock(c.Else)
			a.pushNode(node)
		}
		a.pushBlock(c.Then)
	case *ast.ForStmt:
		f := &forStmt{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: "while",
			Cond: a.parseExpr(x.Cond),
			Block: &block{
				id:    a.newID(),
				Stmts: make([]stmt, 0),
			},
		}
		if x.Init != nil {
			f.Type = "for"
			log.Println("%T", x.Init)
			//f.Init = a.parseExpr(x.Init)
		}
		if x.Post != nil {
			f.Type = "for"
			log.Println("%T", x.Post)
			//f.Post = a.parseExpr(x.Post)
		}
		a.addStmt(f)
		a.pushBlock(f.Block)
	case *ast.File:
	case *ast.BlockStmt:
	case *ast.ExprStmt:
	case *ast.GenDecl:
	default:
		return nil
	}
	a.pushNode(node)
	return a
}
<commit_msg>Comment some debug prints<commit_after>package superast

import (
	"go/ast"
	"go/token"
	"log"
	"strconv"
	"strings"
)

const (
	Default = iota
	IfBody
	IfElse
	FuncBody
)

type AST struct {
	curID      int
	RootBlock  *block
	nodeStack  []ast.Node
	blockStack []*block
	fset       *token.FileSet
	pos        token.Pos
}

func NewAST(fset *token.FileSet) *AST {
	a := &AST{
		fset: fset,
	}
	a.RootBlock = &block{
		id:    a.newID(),
		Stmts: make([]stmt, 0),
	}
	a.pushBlock(a.RootBlock)
	return a
}

func (a *AST) newID() id {
	i := a.curID
	a.curID++
	return id{ID: i}
}

func (a *AST) newPos(p token.Pos) pos {
	position := a.fset.Position(p)
	return pos{Line: position.Line, Col: position.Column}
}

func (a *AST) nodePos(n ast.Node) pos {
	return a.newPos(n.Pos())
}

func (a *AST) curPos() pos {
	return a.newPos(a.pos)
}

func (a *AST) pushNode(node ast.Node) {
	a.nodeStack = append(a.nodeStack, node)
}

func (a *AST) curNode() ast.Node {
	return a.nodeStack[len(a.nodeStack)-1]
}

func (a *AST) popNode() {
	a.nodeStack = a.nodeStack[:len(a.nodeStack)-1]
}

func (a *AST) pushBlock(b *block) {
	a.blockStack = append(a.blockStack, b)
}

func (a *AST) curBlock() *block {
	return a.blockStack[len(a.blockStack)-1]
}

func (a *AST) addStmt(s stmt) {
	b := a.curBlock()
	b.Stmts = append(b.Stmts, s)
}

func (a *AST) popBlock() {
	a.blockStack = a.blockStack[:len(a.blockStack)-1]
}

func exprString(x ast.Expr) string {
	switch t := x.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.BasicLit:
		return t.Value
	case *ast.SelectorExpr:
		return exprString(t.X) + "." + t.Sel.Name
	case *ast.StarExpr:
		return exprString(t.X)
	}
	return ""
}

func exprValue(x ast.Expr) value {
	switch t := x.(type) {
	case *ast.BasicLit:
		switch t.Kind {
		case token.INT:
			i, _ := strconv.ParseInt(t.Value, 10, 0)
			return i
		case token.FLOAT:
			f, _ := strconv.ParseFloat(t.Value, 64)
			return f
		case token.CHAR:
			r, _, _, _ := strconv.UnquoteChar(t.Value, '\'')
			return r
		case token.STRING:
			s, _ := strconv.Unquote(t.Value)
			return s
		}
		return t.Value
	}
	return nil
}

func exprType(x ast.Expr) *dataType {
	if s := exprString(x); s != "" {
		return &dataType{
			Name: s,
		}
	}
	switch t := x.(type) {
	case *ast.ArrayType:
		return &dataType{
			Name:    "vector",
			SubType: exprType(t.Elt),
		}
	}
	return nil
}

type namedType struct {
	vName string
	dType *dataType
	node  ast.Node
}

func flattenNames(baseType ast.Expr, names []*ast.Ident) []namedType {
	t := exprType(baseType)
	if len(names) == 0 {
		return []namedType{
			{vName: "", dType: t},
		}
	}
	var types []namedType
	for _, n := range names {
		types = append(types, namedType{
			vName: n.Name,
			dType: t,
			node:  n,
		})
	}
	return types
}

func flattenFieldList(fieldList *ast.FieldList) []namedType {
	if fieldList == nil {
		return nil
	}
	var types []namedType
	for _, f := range fieldList.List {
		for _, t := range flattenNames(f.Type, f.Names) {
			types = append(types, t)
		}
	}
	return types
}

var basicLitName = map[token.Token]string{
	token.INT:    "int",
	token.FLOAT:  "double",
	token.CHAR:   "char",
	token.STRING: "string",
}

var zeroValues = map[string]value{
	"int":    new(int),
	"double": new(float64),
	"char":   new(rune),
	"string": new(string),
}

func (a *AST) parseExpr(expr ast.Expr) expr {
	switch x := expr.(type) {
	case *ast.Ident:
		return &identifier{
			id:    a.newID(),
			pos:   a.nodePos(x),
			Type:  "identifier",
			Value: x.Name,
		}
	case *ast.BasicLit:
		lType, _ := basicLitName[x.Kind]
		return &identifier{
			id:    a.newID(),
			pos:   a.nodePos(x),
			Type:  lType,
			Value: exprValue(x),
		}
	case *ast.UnaryExpr:
		return &unary{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: x.Op.String(),
			Expr: a.parseExpr(x.X),
		}
	case *ast.CallExpr:
		call := &funcCall{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: "function-call",
			Name: exprString(x.Fun),
		}
		for _, e := range x.Args {
			call.Args = append(call.Args, a.parseExpr(e))
		}
		return call
	case *ast.BinaryExpr:
		return &binary{
			id:    a.newID(),
			pos:   a.nodePos(x),
			Type:  x.Op.String(),
			Left:  a.parseExpr(x.X),
			Right: a.parseExpr(x.Y),
		}
	case *ast.ParenExpr:
		return a.parseExpr(x.X)
	default:
		log.Printf("Unknown expression: %#v", x)
	}
	return nil
}

func (a *AST) assignIdToDataType(dType *dataType) *dataType {
	if dType == nil {
		return nil
	}
	dTypeCopy := *dType
	dTypeCopy.id = a.newID()
	if dTypeCopy.SubType != nil {
		dTypeCopy.SubType = a.assignIdToDataType(dTypeCopy.SubType)
	}
	return &dTypeCopy
}

func (a *AST) Visit(node ast.Node) ast.Visitor {
	if node == nil {
		switch a.curNode().(type) {
		case *ast.BlockStmt:
			a.popBlock()
		}
		a.popNode()
		return nil
	}
	a.pos = node.Pos()
	log.Printf("%s%#v", strings.Repeat("  ", len(a.nodeStack)), node)
	switch x := node.(type) {
	case *ast.TypeSpec:
		n := ""
		if x.Name != nil {
			n = exprString(x.Name)
		}
		switch t := x.Type.(type) {
		case *ast.StructType:
			d := &structDecl{
				id:   a.newID(),
				pos:  a.curPos(),
				Type: "struct-declaration",
				Name: n,
			}
			for _, f := range flattenFieldList(t.Fields) {
				attr := varDecl{
					id:       a.newID(),
					pos:      a.curPos(),
					Type:     "variable-declaration",
					Name:     f.vName,
					DataType: a.assignIdToDataType(f.dType),
				}
				d.Attrs = append(d.Attrs, attr)
			}
			a.addStmt(d)
		}
		return nil
	case *ast.BasicLit:
		l := a.parseExpr(x)
		a.addStmt(l)
		return nil
	case *ast.UnaryExpr:
		u := a.parseExpr(x)
		a.addStmt(u)
		return nil
	case *ast.CallExpr:
		c := a.parseExpr(x)
		a.addStmt(c)
		return nil
	case *ast.FuncDecl:
		name := x.Name.Name
		var retType *dataType
		results := flattenFieldList(x.Type.Results)
		switch len(results) {
		case 1:
			retType = results[0].dType
		}
		d := &funcDecl{
			id:      a.newID(),
			pos:     a.nodePos(x),
			Type:    "function-declaration",
			Name:    name,
			RetType: a.assignIdToDataType(retType),
			Block: &block{
				id:    a.newID(),
				Stmts: make([]stmt, 0),
			},
		}
		for _, f := range flattenFieldList(x.Type.Params) {
			param := varDecl{
				id:       a.newID(),
				pos:      a.nodePos(f.node),
				Type:     "variable-declaration",
				Name:     f.vName,
				DataType: a.assignIdToDataType(f.dType),
			}
			d.Params = append(d.Params, param)
		}
		a.addStmt(d)
		a.pushBlock(d.Block)
	case *ast.DeclStmt:
		gd, _ := x.Decl.(*ast.GenDecl)
		for _, spec := range gd.Specs {
			s, ok := spec.(*ast.ValueSpec)
			if !ok {
				continue
			}
			for i, t := range flattenNames(s.Type, s.Names) {
				vType := t.dType.Name
				v, _ := zeroValues[vType]
				if s.Values != nil {
					v = exprValue(s.Values[i])
				}
				d := &varDecl{
					id:       a.newID(),
					pos:      a.curPos(),
					Type:     "variable-declaration",
					Name:     t.vName,
					DataType: a.assignIdToDataType(t.dType),
					Init: &identifier{
						id:    a.newID(),
						pos:   a.curPos(),
						Type:  vType,
						Value: v,
					},
				}
				a.addStmt(d)
			}
		}
		return nil
	case *ast.AssignStmt:
		for i, l := range x.Lhs {
			r := x.Rhs[i]
			var t string
			switch rx := r.(type) {
			case *ast.BasicLit:
				t, _ = basicLitName[rx.Kind]
			case *ast.CompositeLit:
				t = exprString(rx.Type)
			}
			var s stmt
			if x.Tok == token.DEFINE {
				s = &varDecl{
					id:   a.newID(),
					pos:  a.curPos(),
					Type: "variable-declaration",
					Name: exprString(l),
					DataType: &dataType{
						id:   a.newID(),
						Name: t,
					},
					Init: a.parseExpr(r),
				}
			} else {
				s = &binary{
					id:    a.newID(),
					pos:   a.curPos(),
					Type:  x.Tok.String(),
					Left:  a.parseExpr(l),
					Right: a.parseExpr(r),
				}
			}
			a.addStmt(s)
		}
		return nil
	case *ast.ReturnStmt:
		r := &retStmt{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: "return",
		}
		if len(x.Results) > 0 {
			r.Expr = a.parseExpr(x.Results[0])
		}
		a.addStmt(r)
		return nil
	case *ast.IfStmt:
		c := &conditional{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: "conditional",
			Cond: a.parseExpr(x.Cond),
			Then: &block{
				id:    a.newID(),
				Stmts: make([]stmt, 0),
			},
		}
		a.addStmt(c)
		if x.Else != nil {
			c.Else = &block{
				id:    a.newID(),
				Stmts: make([]stmt, 0),
			}
			a.pushBlock(c.Else)
			a.pushNode(node)
		}
		a.pushBlock(c.Then)
	case *ast.ForStmt:
		f := &forStmt{
			id:   a.newID(),
			pos:  a.nodePos(x),
			Type: "while",
			Cond: a.parseExpr(x.Cond),
			Block: &block{
				id:    a.newID(),
				Stmts: make([]stmt, 0),
			},
		}
		if x.Init != nil {
			f.Type = "for"
			//log.Println("%T", x.Init)
			//f.Init = a.parseExpr(x.Init)
		}
		if x.Post != nil {
			f.Type = "for"
			//log.Println("%T", x.Post)
			//f.Post = a.parseExpr(x.Post)
		}
		a.addStmt(f)
		a.pushBlock(f.Block)
	case *ast.File:
	case *ast.BlockStmt:
	case *ast.ExprStmt:
	case *ast.GenDecl:
	default:
		return nil
	}
	a.pushNode(node)
	return a
}
<|endoftext|>


<commit_before>package watcher

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

var (
	// ErrNothingAdded is an error that occurs when a Watcher's Start() method is
	// called and no files or folders have been added to the Watcher's watchlist.
	ErrNothingAdded = errors.New("error: no files added to the watchlist")

	// ErrWatchedFileDeleted is an error that occurs when a file or folder that was
	// being watched has been deleted.
	ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted")
)

// An EventType is a type that is used to describe what type
// of event has occurred during the watching process.
type EventType int

// EventTypes
const (
	Add EventType = 1 << iota
	Remove
	Modify
	Rename
	Chmod
)

// An Option is a type that is used to set options for a Watcher.
type Option int

const (
	// NonRecursive sets the watcher to not watch directories recursively.
	NonRecursive Option = 1 << iota

	// IgnoreDotFiles sets the watcher to ignore dot files.
	IgnoreDotFiles
)

// An Event desribes an event that is received when files or directory
// changes occur. It includes the os.FileInfo of the changed file or
// directory and the type of event that's occurred and the full path of the file.
type Event struct {
	EventType
	Path string
	os.FileInfo
}

// String returns a string depending on what type of event occurred and the
// file name associated with the event.
func (e Event) String() string {
	pathType := "FILE"
	if e.IsDir() {
		pathType = "DIRECTORY"
	}

	switch e.EventType {
	case Add:
		return fmt.Sprintf("%s %q ADD [%s]", pathType, e.Name(), e.Path)
	case Remove:
		return fmt.Sprintf("%s %q REMOVE [%s]", pathType, e.Name(), e.Path)
	case Modify:
		return fmt.Sprintf("%s %q MODIFY [%s]", pathType, e.Name(), e.Path)
	case Rename:
		return fmt.Sprintf("%s %q RENAME [%s]", pathType, e.Name(), e.Path)
	case Chmod:
		return fmt.Sprintf("%s %q CHMOD [%s]", pathType, e.Name(), e.Path)
	default:
		return "UNRECOGNIZED EVENT"
	}
}

// A Watcher describes a file watcher.
type Watcher struct {
	Event chan Event
	Error chan error

	options []Option

	maxEventsPerCycle int

	// mu protects Files and Names.
	mu    *sync.Mutex
	Files map[string]os.FileInfo
	Names []string
}

// New returns a new initialized *Watcher.
func New(options ...Option) *Watcher {
	return &Watcher{
		Event:   make(chan Event),
		Error:   make(chan error),
		options: options,
		mu:      new(sync.Mutex),
		Files:   make(map[string]os.FileInfo),
		Names:   []string{},
	}
}

// SetMaxEvents controls the maximum amount of events that are sent on
// the Event channel per watching cycle. If max events is less than 1, there is
// no limit, which is the default.
func (w *Watcher) SetMaxEvents(amount int) {
	w.mu.Lock()
	w.maxEventsPerCycle = amount
	w.mu.Unlock()
}

// fileInfo is an implementation of os.FileInfo that can be used
// as a mocked os.FileInfo when triggering an event when the specified
// os.FileInfo is nil.
type fileInfo struct {
	name    string
	size    int64
	mode    os.FileMode
	modTime time.Time
	sys     interface{}
}

func (fs *fileInfo) IsDir() bool {
	return false
}
func (fs *fileInfo) ModTime() time.Time {
	return fs.modTime
}
func (fs *fileInfo) Mode() os.FileMode {
	return fs.mode
}
func (fs *fileInfo) Name() string {
	return fs.name
}
func (fs *fileInfo) Size() int64 {
	return fs.size
}
func (fs *fileInfo) Sys() interface{} {
	return fs.sys
}

// Add adds either a single file or recursed directory to
// the Watcher's file list.
func (w *Watcher) Add(name string) error {
	// Add the name from w's Names list.
	w.mu.Lock()
	w.Names = append(w.Names, name)
	w.mu.Unlock()

	// Make sure name exists.
	fInfo, err := os.Stat(name)
	if err != nil {
		return err
	}

	// If watching a single file, add it and return.
	if !fInfo.IsDir() {
		w.mu.Lock()
		w.Files[fInfo.Name()] = fInfo
		w.mu.Unlock()
		return nil
	}

	// Retrieve a list of all of the os.FileInfo's to add to w.Files.
	fInfoList, err := ListFiles(name, w.options...)
	if err != nil {
		return err
	}
	w.mu.Lock()
	for k, v := range fInfoList {
		w.Files[k] = v
	}
	w.mu.Unlock()
	return nil
}

// Remove removes either a single file or recursed directory from
// the Watcher's file list.
func (w *Watcher) Remove(name string) error {
	// Remove the name from w's Names list.
	w.mu.Lock()
	for i := range w.Names {
		if w.Names[i] == name {
			w.Names = append(w.Names[:i], w.Names[i+1:]...)
		}
	}
	w.mu.Unlock()

	// Make sure name exists.
	fInfo, err := os.Stat(name)
	if err != nil {
		return err
	}

	// If name is a single file, remove it and return.
	if !fInfo.IsDir() {
		w.mu.Lock()
		delete(w.Files, fInfo.Name())
		w.mu.Unlock()
		return nil
	}

	// Retrieve a list of all of the os.FileInfo's to delete from w.Files.
	fInfoList, err := ListFiles(name, w.options...)
	if err != nil {
		return err
	}

	// Remove the appropriate os.FileInfo's from w's os.FileInfo list.
	w.mu.Lock()
	for path := range fInfoList {
		delete(w.Files, path)
	}
	w.mu.Unlock()
	return nil
}

// TriggerEvent is a method that can be used to trigger an event, separate to
// the file watching process.
func (w *Watcher) TriggerEvent(eventType EventType, file os.FileInfo) {
	if file == nil {
		file = &fileInfo{name: "triggered event", modTime: time.Now()}
	}
	w.Event <- Event{EventType: eventType, Path: "-", FileInfo: file}
}

type renamedFrom struct {
	path string
	os.FileInfo
}

// Start starts the watching process and checks for changes every `pollInterval` duration.
// If pollInterval is 0, the default is 100ms.
func (w *Watcher) Start(pollInterval time.Duration) error {
	if pollInterval <= 0 {
		pollInterval = time.Millisecond * 100
	}

	if len(w.Names) < 1 {
		return ErrNothingAdded
	}

	for {
		fileList := make(map[string]os.FileInfo)
		for _, name := range w.Names {
			// Retrieve the list of os.FileInfo's from w.Name.
			list, err := ListFiles(name, w.options...)
			if err != nil {
				if os.IsNotExist(err) {
					w.Error <- ErrWatchedFileDeleted
					// TODO: remove and continue if there is still
					// more than 1 file left after removal.
				} else {
					w.Error <- err
				}
			}
			for k, v := range list {
				fileList[k] = v
			}
		}

		numEvents := 0

		events := map[EventType]map[string]os.FileInfo{
			Add:    make(map[string]os.FileInfo),
			Remove: make(map[string]os.FileInfo),
		}

		renamed := make(map[string]renamedFrom)

		// Check for added files.
		for path, file := range fileList {
			if _, found := w.Files[path]; !found {
				events[Add][path] = file
			}
		}

		// Check for removed files.
		for path, file := range w.Files {
			if _, found := fileList[path]; !found {
				events[Remove][path] = file
			}
		}

		// Check for renamed files.
		for path1, file1 := range events[Add] {
			if w.maxEventsPerCycle > 0 && numEvents >= w.maxEventsPerCycle {
				goto SLEEP
			}
			for path2, file2 := range events[Remove] {
				if file1.Size() == file2.Size() && path1 != path2 &&
					filepath.Dir(path1) == filepath.Dir(path2) &&
					file1.IsDir() == file2.IsDir() &&
					file1.ModTime() == file2.ModTime() { // TODO: Check this <--
					renamed[path2] = renamedFrom{path1, file1}
					w.Event <- Event{
						EventType: Rename,
						Path:      path2,
						FileInfo:  file2,
					}
					numEvents++

					// Delete path1 from the added files map.
					delete(events[Add], path1)

					// Delete path2 from the deleted files map.
					delete(events[Remove], path2)
				}
			}
		}

		for path, file := range events[Add] {
			if w.maxEventsPerCycle > 0 && numEvents >= w.maxEventsPerCycle {
				goto SLEEP
			}
			w.Event <- Event{
				EventType: Add,
				Path:      path,
				FileInfo:  file,
			}
			numEvents++
		}

		for path, file := range events[Remove] {
			if w.maxEventsPerCycle > 0 && numEvents >= w.maxEventsPerCycle {
				goto SLEEP
			}
			w.Event <- Event{
				EventType: Remove,
				Path:      path,
				FileInfo:  file,
			}
			numEvents++
		}

		// Check for modified files.
		for path, file := range w.Files {
			if w.maxEventsPerCycle > 0 && numEvents >= w.maxEventsPerCycle {
				goto SLEEP
			}
			_, addFound := events[Add][path]
			_, removeFound := events[Remove][path]
			renamedFrom, renameFound := renamed[path]
			if !addFound && !removeFound && !renameFound {
				if !file.IsDir() && fileList[path].ModTime() != file.ModTime() {
					w.Event <- Event{
						EventType: Modify,
						Path:      path,
						FileInfo:  file,
					}
					numEvents++
				}

				if fileList[path].Mode() != file.Mode() {
					w.Event <- Event{
						EventType: Chmod,
						Path:      path,
						FileInfo:  file,
					}
					numEvents++
				}
			}
			if renameFound && renamedFrom.Mode() != file.Mode() {
				w.Event <- Event{
					EventType: Chmod,
					Path:      renamedFrom.path,
					FileInfo:  renamedFrom.FileInfo,
				}
				numEvents++
			}
		}

	SLEEP:
		// Update w.Files and then sleep for a little bit.
		w.Files = fileList
		time.Sleep(pollInterval)
	}
}

// hasOption returns true or false based on whether or not
// an Option exists in an Option slice.
func hasOption(options []Option, option Option) bool {
	for _, o := range options {
		if option&o != 0 {
			return true
		}
	}
	return false
}

// ListFiles returns a map of all os.FileInfo's recursively
// contained in a directory. If name is a single file, it returns
// an os.FileInfo map containing a single os.FileInfo.
func ListFiles(name string, options ...Option) (map[string]os.FileInfo, error) {
	fileList := make(map[string]os.FileInfo)

	name = filepath.Clean(name)

	nonRecursive := hasOption(options, NonRecursive)
	ignoreDotFiles := hasOption(options, IgnoreDotFiles)

	if nonRecursive {
		f, err := os.Open(name)
		if err != nil {
			return nil, err
		}
		defer f.Close()
		info, err := os.Stat(name)
		if err != nil {
			return nil, err
		}
		// Add the name to fileList.
		if !info.IsDir() && ignoreDotFiles && strings.HasPrefix(name, ".") {
			return fileList, nil
		}
		fileList[name] = info
		if !info.IsDir() {
			return fileList, nil
		}
		// It's a directory, read it's contents.
		fInfoList, err := f.Readdir(-1)
		if err != nil {
			return nil, err
		}
		// Add all of the FileInfo's returned from f.ReadDir to fileList.
		for _, fInfo := range fInfoList {
			if ignoreDotFiles && strings.HasPrefix(fInfo.Name(), ".") {
				continue
			}
			fileList[filepath.Join(name, fInfo.Name())] = fInfo
		}
		return fileList, nil
	}

	if err := filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if ignoreDotFiles && strings.HasPrefix(info.Name(), ".") {
			if info.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}

		fileList[path] = info

		return nil
	}); err != nil {
		return nil, err
	}

	return fileList, nil
}
<commit_msg>added String method for EventType<commit_after>package watcher

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

var (
	// ErrNothingAdded is an error that occurs when a Watcher's Start() method is
	// called and no files or folders have been added to the Watcher's watchlist.
	ErrNothingAdded = errors.New("error: no files added to the watchlist")

	// ErrWatchedFileDeleted is an error that occurs when a file or folder that was
	// being watched has been deleted.
	ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted")
)

// An EventType is a type that is used to describe what type
// of event has occurred during the watching process.
type EventType int

// EventTypes
const (
	Add EventType = 1 << iota
	Remove
	Modify
	Rename
	Chmod
)

var eventTypes = map[EventType]string{
	Add:    "ADD",
	Remove: "REMOVE",
	Modify: "MODIFY",
	Rename: "RENAME",
	Chmod:  "CHMOD",
}

// String prints the string version of the EventType consts
func (e EventType) String() string {
	return eventTypes[e]
}

// An Option is a type that is used to set options for a Watcher.
type Option int

const (
	// NonRecursive sets the watcher to not watch directories recursively.
	NonRecursive Option = 1 << iota

	// IgnoreDotFiles sets the watcher to ignore dot files.
	IgnoreDotFiles
)

// An Event desribes an event that is received when files or directory
// changes occur. It includes the os.FileInfo of the changed file or
// directory and the type of event that's occurred and the full path of the file.
type Event struct {
	EventType
	Path string
	os.FileInfo
}

// String returns a string depending on what type of event occurred and the
// file name associated with the event.
func (e Event) String() string {
	pathType := "FILE"
	if e.IsDir() {
		pathType = "DIRECTORY"
	}

	switch e.EventType {
	case Add:
		return fmt.Sprintf("%s %q ADD [%s]", pathType, e.Name(), e.Path)
	case Remove:
		return fmt.Sprintf("%s %q REMOVE [%s]", pathType, e.Name(), e.Path)
	case Modify:
		return fmt.Sprintf("%s %q MODIFY [%s]", pathType, e.Name(), e.Path)
	case Rename:
		return fmt.Sprintf("%s %q RENAME [%s]", pathType, e.Name(), e.Path)
	case Chmod:
		return fmt.Sprintf("%s %q CHMOD [%s]", pathType, e.Name(), e.Path)
	default:
		return "UNRECOGNIZED EVENT"
	}
}

// A Watcher describes a file watcher.
type Watcher struct {
	Event chan Event
	Error chan error

	options []Option

	maxEventsPerCycle int

	// mu protects Files and Names.
	mu    *sync.Mutex
	Files map[string]os.FileInfo
	Names []string
}

// New returns a new initialized *Watcher.
func New(options ...Option) *Watcher {
	return &Watcher{
		Event:   make(chan Event),
		Error:   make(chan error),
		options: options,
		mu:      new(sync.Mutex),
		Files:   make(map[string]os.FileInfo),
		Names:   []string{},
	}
}

// SetMaxEvents controls the maximum amount of events that are sent on
// the Event channel per watching cycle. If max events is less than 1, there is
// no limit, which is the default.
func (w *Watcher) SetMaxEvents(amount int) {
	w.mu.Lock()
	w.maxEventsPerCycle = amount
	w.mu.Unlock()
}

// fileInfo is an implementation of os.FileInfo that can be used
// as a mocked os.FileInfo when triggering an event when the specified
// os.FileInfo is nil.
type fileInfo struct {
	name    string
	size    int64
	mode    os.FileMode
	modTime time.Time
	sys     interface{}
}

func (fs *fileInfo) IsDir() bool {
	return false
}
func (fs *fileInfo) ModTime() time.Time {
	return fs.modTime
}
func (fs *fileInfo) Mode() os.FileMode {
	return fs.mode
}
func (fs *fileInfo) Name() string {
	return fs.name
}
func (fs *fileInfo) Size() int64 {
	return fs.size
}
func (fs *fileInfo) Sys() interface{} {
	return fs.sys
}

// Add adds either a single file or recursed directory to
// the Watcher's file list.
func (w *Watcher) Add(name string) error {
	// Add the name from w's Names list.
	w.mu.Lock()
	w.Names = append(w.Names, name)
	w.mu.Unlock()

	// Make sure name exists.
	fInfo, err := os.Stat(name)
	if err != nil {
		return err
	}

	// If watching a single file, add it and return.
	if !fInfo.IsDir() {
		w.mu.Lock()
		w.Files[fInfo.Name()] = fInfo
		w.mu.Unlock()
		return nil
	}

	// Retrieve a list of all of the os.FileInfo's to add to w.Files.
	fInfoList, err := ListFiles(name, w.options...)
	if err != nil {
		return err
	}
	w.mu.Lock()
	for k, v := range fInfoList {
		w.Files[k] = v
	}
	w.mu.Unlock()
	return nil
}

// Remove removes either a single file or recursed directory from
// the Watcher's file list.
func (w *Watcher) Remove(name string) error {
	// Remove the name from w's Names list.
	w.mu.Lock()
	for i := range w.Names {
		if w.Names[i] == name {
			w.Names = append(w.Names[:i], w.Names[i+1:]...)
		}
	}
	w.mu.Unlock()

	// Make sure name exists.
	fInfo, err := os.Stat(name)
	if err != nil {
		return err
	}

	// If name is a single file, remove it and return.
	if !fInfo.IsDir() {
		w.mu.Lock()
		delete(w.Files, fInfo.Name())
		w.mu.Unlock()
		return nil
	}

	// Retrieve a list of all of the os.FileInfo's to delete from w.Files.
	fInfoList, err := ListFiles(name, w.options...)
	if err != nil {
		return err
	}

	// Remove the appropriate os.FileInfo's from w's os.FileInfo list.
	w.mu.Lock()
	for path := range fInfoList {
		delete(w.Files, path)
	}
	w.mu.Unlock()
	return nil
}

// TriggerEvent is a method that can be used to trigger an event, separate to
// the file watching process.
func (w *Watcher) TriggerEvent(eventType EventType, file os.FileInfo) {
	if file == nil {
		file = &fileInfo{name: "triggered event", modTime: time.Now()}
	}
	w.Event <- Event{EventType: eventType, Path: "-", FileInfo: file}
}

type renamedFrom struct {
	path string
	os.FileInfo
}

// Start starts the watching process and checks for changes every `pollInterval` duration.
// If pollInterval is 0, the default is 100ms.
func (w *Watcher) Start(pollInterval time.Duration) error {
	if pollInterval <= 0 {
		pollInterval = time.Millisecond * 100
	}

	if len(w.Names) < 1 {
		return ErrNothingAdded
	}

	for {
		fileList := make(map[string]os.FileInfo)
		for _, name := range w.Names {
			// Retrieve the list of os.FileInfo's from w.Name.
			list, err := ListFiles(name, w.options...)
			if err != nil {
				if os.IsNotExist(err) {
					w.Error <- ErrWatchedFileDeleted
					// TODO: remove and continue if there is still
					// more than 1 file left after removal.
				} else {
					w.Error <- err
				}
			}
			for k, v := range list {
				fileList[k] = v
			}
		}

		numEvents := 0

		events := map[EventType]map[string]os.FileInfo{
			Add:    make(map[string]os.FileInfo),
			Remove: make(map[string]os.FileInfo),
		}

		renamed := make(map[string]renamedFrom)

		// Check for added files.
		for path, file := range fileList {
			if _, found := w.Files[path]; !found {
				events[Add][path] = file
			}
		}

		// Check for removed files.
		for path, file := range w.Files {
			if _, found := fileList[path]; !found {
				events[Remove][path] = file
			}
		}

		// Check for renamed files.
		for path1, file1 := range events[Add] {
			if w.maxEventsPerCycle > 0 && numEvents >= w.maxEventsPerCycle {
				goto SLEEP
			}
			for path2, file2 := range events[Remove] {
				if file1.Size() == file2.Size() && path1 != path2 &&
					filepath.Dir(path1) == filepath.Dir(path2) &&
					file1.IsDir() == file2.IsDir() &&
					file1.ModTime() == file2.ModTime() { // TODO: Check this <--
					renamed[path2] = renamedFrom{path1, file1}
					w.Event <- Event{
						EventType: Rename,
						Path:      path2,
						FileInfo:  file2,
					}
					numEvents++

					// Delete path1 from the added files map.
					delete(events[Add], path1)

					// Delete path2 from the deleted files map.
					delete(events[Remove], path2)
				}
			}
		}

		for path, file := range events[Add] {
			if w.maxEventsPerCycle > 0 && numEvents >= w.maxEventsPerCycle {
				goto SLEEP
			}
			w.Event <- Event{
				EventType: Add,
				Path:      path,
				FileInfo:  file,
			}
			numEvents++
		}

		for path, file := range events[Remove] {
			if w.maxEventsPerCycle > 0 && numEvents >= w.maxEventsPerCycle {
				goto SLEEP
			}
			w.Event <- Event{
				EventType: Remove,
				Path:      path,
				FileInfo:  file,
			}
			numEvents++
		}

		// Check for modified files.
		for path, file := range w.Files {
			if w.maxEventsPerCycle > 0 && numEvents >= w.maxEventsPerCycle {
				goto SLEEP
			}
			_, addFound := events[Add][path]
			_, removeFound := events[Remove][path]
			renamedFrom, renameFound := renamed[path]
			if !addFound && !removeFound && !renameFound {
				if !file.IsDir() && fileList[path].ModTime() != file.ModTime() {
					w.Event <- Event{
						EventType: Modify,
						Path:      path,
						FileInfo:  file,
					}
					numEvents++
				}

				if fileList[path].Mode() != file.Mode() {
					w.Event <- Event{
						EventType: Chmod,
						Path:      path,
						FileInfo:  file,
					}
					numEvents++
				}
			}
			if renameFound && renamedFrom.Mode() != file.Mode() {
				w.Event <- Event{
					EventType: Chmod,
					Path:      renamedFrom.path,
					FileInfo:  renamedFrom.FileInfo,
				}
				numEvents++
			}
		}

	SLEEP:
		// Update w.Files and then sleep for a little bit.
		w.Files = fileList
		time.Sleep(pollInterval)
	}
}

// hasOption returns true or false based on whether or not
// an Option exists in an Option slice.
func hasOption(options []Option, option Option) bool {
	for _, o := range options {
		if option&o != 0 {
			return true
		}
	}
	return false
}

// ListFiles returns a map of all os.FileInfo's recursively
// contained in a directory. If name is a single file, it returns
// an os.FileInfo map containing a single os.FileInfo.
func ListFiles(name string, options ...Option) (map[string]os.FileInfo, error) {
	fileList := make(map[string]os.FileInfo)

	name = filepath.Clean(name)

	nonRecursive := hasOption(options, NonRecursive)
	ignoreDotFiles := hasOption(options, IgnoreDotFiles)

	if nonRecursive {
		f, err := os.Open(name)
		if err != nil {
			return nil, err
		}
		defer f.Close()
		info, err := os.Stat(name)
		if err != nil {
			return nil, err
		}
		// Add the name to fileList.
		if !info.IsDir() && ignoreDotFiles && strings.HasPrefix(name, ".") {
			return fileList, nil
		}
		fileList[name] = info
		if !info.IsDir() {
			return fileList, nil
		}
		// It's a directory, read it's contents.
		fInfoList, err := f.Readdir(-1)
		if err != nil {
			return nil, err
		}
		// Add all of the FileInfo's returned from f.ReadDir to fileList.
		for _, fInfo := range fInfoList {
			if ignoreDotFiles && strings.HasPrefix(fInfo.Name(), ".") {
				continue
			}
			fileList[filepath.Join(name, fInfo.Name())] = fInfo
		}
		return fileList, nil
	}

	if err := filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if ignoreDotFiles && strings.HasPrefix(info.Name(), ".") {
			if info.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}

		fileList[path] = info

		return nil
	}); err != nil {
		return nil, err
	}

	return fileList, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 WALLIX

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

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

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

package aws

import (
	"errors"
	"net/http"
	"time"

	awssdk "github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/logger"
)

var (
	AccessService, InfraService, StorageService, NotificationService, QueueService, DnsService, LambdaService, MonitoringService, CdnService, CloudformationService cloud.Service
)

func InitServices(conf map[string]interface{}, log *logger.Logger) error {
	awsconf := config(conf)
	region := awsconf.region()
	if region == "" {
		return errors.New("empty AWS region. Set it with `awless config set aws.region`")
	}

	sess, err := initAWSSession(region, awsconf.profile())
	if err != nil {
		return err
	}

	AccessService = NewAccess(sess, awsconf, log)
	InfraService = NewInfra(sess, awsconf, log)
	StorageService = NewStorage(sess, awsconf, log)
	NotificationService = NewNotification(sess, awsconf, log)
	QueueService = NewQueue(sess, awsconf, log)
	DnsService = NewDns(sess, awsconf, log)
	LambdaService = NewLambda(sess, awsconf, log)
	MonitoringService = NewMonitoring(sess, awsconf, log)
	CdnService = NewCdn(sess, awsconf, log)
	CloudformationService = NewCloudformation(sess, awsconf, log)

	cloud.ServiceRegistry[InfraService.Name()] = InfraService
	cloud.ServiceRegistry[AccessService.Name()] = AccessService
	cloud.ServiceRegistry[StorageService.Name()] = StorageService
	cloud.ServiceRegistry[NotificationService.Name()] = NotificationService
	cloud.ServiceRegistry[QueueService.Name()] = QueueService
	cloud.ServiceRegistry[DnsService.Name()] = DnsService
	cloud.ServiceRegistry[LambdaService.Name()] = LambdaService
	cloud.ServiceRegistry[MonitoringService.Name()] = MonitoringService
	cloud.ServiceRegistry[CdnService.Name()] = CdnService
	cloud.ServiceRegistry[CloudformationService.Name()] = CloudformationService

	return nil
}

func initAWSSession(region, profile string) (*session.Session, error) {
	session, err := session.NewSessionWithOptions(session.Options{
		Config:                  awssdk.Config{Region: awssdk.String(region), HTTPClient: &http.Client{Timeout: 2 * time.Second}},
		SharedConfigState:       session.SharedConfigEnable,
		AssumeRoleTokenProvider: stscreds.StdinTokenProvider,
		Profile:                 profile,
	})
	//session.Config = session.Config.WithLogLevel(awssdk.LogDebugWithHTTPBody)
	if err != nil {
		return nil, err
	}

	if _, err = session.Config.Credentials.Get(); err != nil {
		return nil, errors.New("Your AWS credentials seem undefined! AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY need to be exported in your CLI environment\nInstallation documentation is at https://github.com/wallix/awless/wiki/Installation")
	}
	session.Config.HTTPClient = http.DefaultClient

	return session, nil
}
<commit_msg>Revert "Removing suprinsigly unused method in init process"<commit_after>/*
Copyright 2017 WALLIX

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

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

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

package aws

import (
	"errors"
	"fmt"
	"net/http"
	"time"

	awssdk "github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/wallix/awless/aws/config"
	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/logger"
	"github.com/wallix/awless/template/driver"
)

var (
	AccessService, InfraService, StorageService, NotificationService, QueueService, DnsService, LambdaService, MonitoringService, CdnService, CloudformationService cloud.Service
)

func InitServices(conf map[string]interface{}, log *logger.Logger) error {
	awsconf := config(conf)
	region := awsconf.region()
	if region == "" {
		return errors.New("empty AWS region. Set it with `awless config set aws.region`")
	}

	sess, err := initAWSSession(region, awsconf.profile())
	if err != nil {
		return err
	}

	AccessService = NewAccess(sess, awsconf, log)
	InfraService = NewInfra(sess, awsconf, log)
	StorageService = NewStorage(sess, awsconf, log)
	NotificationService = NewNotification(sess, awsconf, log)
	QueueService = NewQueue(sess, awsconf, log)
	DnsService = NewDns(sess, awsconf, log)
	LambdaService = NewLambda(sess, awsconf, log)
	MonitoringService = NewMonitoring(sess, awsconf, log)
	CdnService = NewCdn(sess, awsconf, log)
	CloudformationService = NewCloudformation(sess, awsconf, log)

	cloud.ServiceRegistry[InfraService.Name()] = InfraService
	cloud.ServiceRegistry[AccessService.Name()] = AccessService
	cloud.ServiceRegistry[StorageService.Name()] = StorageService
	cloud.ServiceRegistry[NotificationService.Name()] = NotificationService
	cloud.ServiceRegistry[QueueService.Name()] = QueueService
	cloud.ServiceRegistry[DnsService.Name()] = DnsService
	cloud.ServiceRegistry[LambdaService.Name()] = LambdaService
	cloud.ServiceRegistry[MonitoringService.Name()] = MonitoringService
	cloud.ServiceRegistry[CdnService.Name()] = CdnService
	cloud.ServiceRegistry[CloudformationService.Name()] = CloudformationService

	return nil
}

func NewDriver(region, profile string, log ...*logger.Logger) (driver.Driver, error) {
	if !awsconfig.IsValidRegion(region) {
		return nil, fmt.Errorf("invalid region '%s' provided", region)
	}

	sess, err := initAWSSession(region, profile)
	if err != nil {
		return nil, err
	}

	drivLog := logger.DiscardLogger
	if len(log) > 0 {
		drivLog = log[0]
	}

	awsconf := config(
		map[string]interface{}{"aws.region": region, "aws.profile": profile},
	)

	var drivers []driver.Driver
	drivers = append(drivers, NewAccess(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewInfra(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewStorage(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewNotification(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewQueue(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewDns(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewLambda(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewMonitoring(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewCdn(sess, awsconf, drivLog).Drivers()...)
	drivers = append(drivers, NewCloudformation(sess, awsconf, drivLog).Drivers()...)

	return driver.NewMultiDriver(drivers...), nil
}

func initAWSSession(region, profile string) (*session.Session, error) {
	session, err := session.NewSessionWithOptions(session.Options{
		Config:                  awssdk.Config{Region: awssdk.String(region), HTTPClient: &http.Client{Timeout: 2 * time.Second}},
		SharedConfigState:       session.SharedConfigEnable,
		AssumeRoleTokenProvider: stscreds.StdinTokenProvider,
		Profile:                 profile,
	})
	//session.Config = session.Config.WithLogLevel(awssdk.LogDebugWithHTTPBody)
	if err != nil {
		return nil, err
	}

	if _, err = session.Config.Credentials.Get(); err != nil {
		return nil, errors.New("Your AWS credentials seem undefined! AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY need to be exported in your CLI environment\nInstallation documentation is at https://github.com/wallix/awless/wiki/Installation")
	}
	session.Config.HTTPClient = http.DefaultClient

	return session, nil
}
<|endoftext|>


<commit_before>package sudoku

type swordfishTechnique struct {
	*basicSolveTechnique
}

func (self *swordfishTechnique) humanLikelihood(step *SolveStep) float64 {
	//TODO: reason more carefully about how hard this technique is.
	return self.difficultyHelper(70.0)
}

func (self *swordfishTechnique) Description(step *SolveStep) string {
	//TODO: Implement this
	return "TODO: IMPLEMENT ME"
}

func (self *swordfishTechnique) Find(grid *Grid, results chan *SolveStep, done chan bool) {

	getter := self.getter(grid)

	//For this technique, the primary access is the first type of group we look at to find
	//cells with only 2 spots for a given number.

	//TODO: Implement the "relaxed" version of this technique, too.

	//TODO: walk through this in random order
	for i := 1; i < DIM+1; i++ {
		//The candidate we're considering

		//Consider each of the major-axis groups to see if more than three have
		//only two candidates for

		var majorAxisGroupsWithTwoOptionsForCandidate []CellSlice

		for c := 0; c < DIM; c++ {
			majorAxisGroup := getter(c)

			cellsWithCandidatePossibility := majorAxisGroup.FilterByPossible(i)

			if len(cellsWithCandidatePossibility) == 2 {
				//TODO: shouldn't we keep track of the rows where the possibilities were, too?
				majorAxisGroupsWithTwoOptionsForCandidate = append(majorAxisGroupsWithTwoOptionsForCandidate, cellsWithCandidatePossibility)
			}

		}

		//Do we have more than three major axis groups identified?
		if len(majorAxisGroupsWithTwoOptionsForCandidate) < 3 {
			continue
		}

		//Consider every subset of size three
		for _, indexes := range subsetIndexes(len(majorAxisGroupsWithTwoOptionsForCandidate), 3) {
			majorAxisGroups := make([]CellSlice, 3)
			for i, index := range indexes {
				majorAxisGroups[i] = majorAxisGroupsWithTwoOptionsForCandidate[index]
			}

			//OK, now majorAxisGroups has the set of three we're operating on.
			//Do their minorAxis groups line up to a set of three as well?

			var minorGroupIndexSet intSet

			for _, group := range majorAxisGroups {
				if self.groupType == _GROUP_COL {
					minorGroupIndexSet = minorGroupIndexSet.union(group.AllRows().toIntSet())
				} else {
					minorGroupIndexSet = minorGroupIndexSet.union(group.AllCols().toIntSet())
				}
			}

			if len(minorGroupIndexSet) != 3 {
				//Nah, didn't have three rows.
				continue
			}

			//Woot, looks like we found a valid set.

			//Generate the list of cells that will be affected
			var affectedCells CellSlice

			for minorGroupIndex, _ := range minorGroupIndexSet {
				if self.groupType == _GROUP_COL {
					affectedCells = append(affectedCells, grid.Row(minorGroupIndex)...)
				} else {
					affectedCells = append(affectedCells, grid.Col(minorGroupIndex)...)
				}
			}

			//Get rid of cells that are already filled, or where removing 'i'
			//would be a no-op anyway since it's not already a possible there.
			affectedCells = affectedCells.FilterByPossible(i)

			//Gather the list of all cells that are "pointing" to this

			var pointerCells CellSlice

			for _, group := range majorAxisGroups {
				pointerCells = append(pointerCells, group...)
			}

			//Get rid of all pointer cells from the rows of cells that will be
			//affected.
			affectedCells = affectedCells.RemoveCells(pointerCells)

			//Okay, now the set is solid.

			//Okay, we have a candidate step (unchunked). Is it useful?
			step := &SolveStep{self,
				affectedCells,
				IntSlice{i},
				pointerCells,
				nil,
				nil,
			}

			if step.IsUseful(grid) {
				select {
				case results <- step:
				case <-done:
					return
				}
			}

		}

	}

}
<commit_msg>Added a minor TODO<commit_after>package sudoku

type swordfishTechnique struct {
	*basicSolveTechnique
}

func (self *swordfishTechnique) humanLikelihood(step *SolveStep) float64 {
	//TODO: reason more carefully about how hard this technique is.
	return self.difficultyHelper(70.0)
}

func (self *swordfishTechnique) Description(step *SolveStep) string {
	//TODO: Implement this
	return "TODO: IMPLEMENT ME"
}

func (self *swordfishTechnique) Find(grid *Grid, results chan *SolveStep, done chan bool) {

	getter := self.getter(grid)

	//For this technique, the primary access is the first type of group we look at to find
	//cells with only 2 spots for a given number.

	//TODO: Implement the "relaxed" version of this technique, too.

	//TODO: walk through this in random order
	//... and do this in xywing, too.
	for i := 1; i < DIM+1; i++ {
		//The candidate we're considering

		//Consider each of the major-axis groups to see if more than three have
		//only two candidates for

		var majorAxisGroupsWithTwoOptionsForCandidate []CellSlice

		for c := 0; c < DIM; c++ {
			majorAxisGroup := getter(c)

			cellsWithCandidatePossibility := majorAxisGroup.FilterByPossible(i)

			if len(cellsWithCandidatePossibility) == 2 {
				//TODO: shouldn't we keep track of the rows where the possibilities were, too?
				majorAxisGroupsWithTwoOptionsForCandidate = append(majorAxisGroupsWithTwoOptionsForCandidate, cellsWithCandidatePossibility)
			}

		}

		//Do we have more than three major axis groups identified?
		if len(majorAxisGroupsWithTwoOptionsForCandidate) < 3 {
			continue
		}

		//Consider every subset of size three
		for _, indexes := range subsetIndexes(len(majorAxisGroupsWithTwoOptionsForCandidate), 3) {
			majorAxisGroups := make([]CellSlice, 3)
			for i, index := range indexes {
				majorAxisGroups[i] = majorAxisGroupsWithTwoOptionsForCandidate[index]
			}

			//OK, now majorAxisGroups has the set of three we're operating on.
			//Do their minorAxis groups line up to a set of three as well?

			var minorGroupIndexSet intSet

			for _, group := range majorAxisGroups {
				if self.groupType == _GROUP_COL {
					minorGroupIndexSet = minorGroupIndexSet.union(group.AllRows().toIntSet())
				} else {
					minorGroupIndexSet = minorGroupIndexSet.union(group.AllCols().toIntSet())
				}
			}

			if len(minorGroupIndexSet) != 3 {
				//Nah, didn't have three rows.
				continue
			}

			//Woot, looks like we found a valid set.

			//Generate the list of cells that will be affected
			var affectedCells CellSlice

			for minorGroupIndex, _ := range minorGroupIndexSet {
				if self.groupType == _GROUP_COL {
					affectedCells = append(affectedCells, grid.Row(minorGroupIndex)...)
				} else {
					affectedCells = append(affectedCells, grid.Col(minorGroupIndex)...)
				}
			}

			//Get rid of cells that are already filled, or where removing 'i'
			//would be a no-op anyway since it's not already a possible there.
			affectedCells = affectedCells.FilterByPossible(i)

			//Gather the list of all cells that are "pointing" to this

			var pointerCells CellSlice

			for _, group := range majorAxisGroups {
				pointerCells = append(pointerCells, group...)
			}

			//Get rid of all pointer cells from the rows of cells that will be
			//affected.
			affectedCells = affectedCells.RemoveCells(pointerCells)

			//Okay, now the set is solid.

			//Okay, we have a candidate step (unchunked). Is it useful?
			step := &SolveStep{self,
				affectedCells,
				IntSlice{i},
				pointerCells,
				nil,
				nil,
			}

			if step.IsUseful(grid) {
				select {
				case results <- step:
				case <-done:
					return
				}
			}

		}

	}

}
<|endoftext|>