<commit_before>package drivers

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	"golang.org/x/sys/unix"

	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/ioprogress"
	"github.com/lxc/lxd/shared/logger"
)

// Errors
var errBtrfsNoQuota = fmt.Errorf("Quotas disabled on filesystem")
var errBtrfsNoQGroup = fmt.Errorf("Unable to find quota group")

func (d *btrfs) getMountOptions() string {
	// Allow overriding the default options.
	if d.config["btrfs.mount_options"] != "" {
		return d.config["btrfs.mount_options"]
	}

	return "user_subvol_rm_allowed"
}

func (d *btrfs) isSubvolume(path string) bool {
	// Stat the path.
	fs := unix.Stat_t{}
	err := unix.Lstat(path, &fs)
	if err != nil {
		return false
	}

	// Check if BTRFS_FIRST_FREE_OBJECTID is the inode number.
	if fs.Ino != 256 {
		return false
	}

	return true
}

func (d *btrfs) getSubvolumes(path string) ([]string, error) {
	result := []string{}

	// Make sure the path has a trailing slash.
	if !strings.HasSuffix(path, "/") {
		path = path + "/"
	}

	// Walk through the entire tree looking for subvolumes.
	err := filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Ignore the base path.
		if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
			return nil
		}

		// Subvolumes can only be directories.
		if !fi.IsDir() {
			return nil
		}

		// Check if a subvolume.
		if d.isSubvolume(fpath) {
			result = append(result, strings.TrimPrefix(fpath, path))
		}

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

	return result, nil
}

// snapshotSubvolume creates a snapshot of the specified path at the dest supplied. If recursion is true and
// sub volumes are found below the path then they are created at the relative location in dest.
func (d *btrfs) snapshotSubvolume(path string, dest string, recursion bool) error {
	// Single subvolume deletion.
	snapshot := func(path string, dest string) error {
		_, err := shared.RunCommand("btrfs", "subvolume", "snapshot", path, dest)
		if err != nil {
			return err
		}

		return nil
	}

	// First snapshot the root.
	err := snapshot(path, dest)
	if err != nil {
		return err
	}

	// Now snapshot all subvolumes of the root.
	if recursion {
		// Get the subvolumes list.
		subSubVols, err := d.getSubvolumes(path)
		if err != nil {
			return err
		}
		sort.Sort(sort.StringSlice(subSubVols))

		for _, subSubVol := range subSubVols {
			subSubVolSnapPath := filepath.Join(dest, subSubVol)

			// Clear the target for the subvol to use.
			os.Remove(subSubVolSnapPath)

			err := snapshot(filepath.Join(path, subSubVol), subSubVolSnapPath)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (d *btrfs) deleteSubvolume(path string, recursion bool) error {
	// Single subvolume deletion.
	destroy := func(path string) error {
		// Attempt (but don't fail on) to delete any qgroup on the subvolume.
		qgroup, _, err := d.getQGroup(path)
		if err == nil {
			shared.RunCommand("btrfs", "qgroup", "destroy", qgroup, path)
		}

		// Attempt to make the subvolume writable.
		d.setSubvolumeReadonlyProperty(path, false)

		// Temporarily change ownership & mode to help with nesting.
		os.Chmod(path, 0700)
		os.Chown(path, 0, 0)

		// Delete the subvolume itself.
		_, err = shared.RunCommand("btrfs", "subvolume", "delete", path)

		return err
	}

	// Delete subsubvols.
	if recursion {
		// Get the subvolumes list.
		subsubvols, err := d.getSubvolumes(path)
		if err != nil {
			return err
		}
		sort.Sort(sort.Reverse(sort.StringSlice(subsubvols)))

		for _, subsubvol := range subsubvols {
			err := destroy(filepath.Join(path, subsubvol))
			if err != nil {
				return err
			}
		}
	}

	// Delete the subvol itself.
	err := destroy(path)
	if err != nil {
		return err
	}

	return nil
}

func (d *btrfs) getQGroup(path string) (string, int64, error) {
	// Try to get the qgroup details.
	output, err := shared.RunCommand("btrfs", "qgroup", "show", "-e", "-f", "--raw", path)
	if err != nil {
		return "", -1, errBtrfsNoQuota
	}

	// Parse to extract the qgroup identifier.
	var qgroup string
	usage := int64(-1)
	for _, line := range strings.Split(output, "\n") {
		if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
			continue
		}

		fields := strings.Fields(line)
		if len(fields) != 4 {
			continue
		}

		qgroup = fields[0]
		val, err := strconv.ParseInt(fields[2], 10, 64)
		if err == nil {
			usage = val
		}

		break
	}

	if qgroup == "" {
		return "", -1, errBtrfsNoQGroup
	}

	return qgroup, usage, nil
}

func (d *btrfs) sendSubvolume(path string, parent string, conn io.ReadWriteCloser, tracker *ioprogress.ProgressTracker) error {
	// Assemble btrfs send command.
	args := []string{"send"}
	if parent != "" {
		args = append(args, "-p", parent)
	}
	args = append(args, path)
	cmd := exec.Command("btrfs", args...)

	// Prepare stdout/stderr.
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	// Setup progress tracker.
	stdoutPipe := stdout
	if tracker != nil {
		stdoutPipe = &ioprogress.ProgressReader{
			ReadCloser: stdout,
			Tracker:    tracker,
		}
	}

	// Forward any output on stdout.
	chStdoutPipe := make(chan error, 1)
	go func() {
		_, err := io.Copy(conn, stdoutPipe)
		chStdoutPipe <- err
		conn.Close()
	}()

	// Run the command.
	err = cmd.Start()
	if err != nil {
		return err
	}

	// Read any error.
	output, err := ioutil.ReadAll(stderr)
	if err != nil {
		logger.Errorf("Problem reading btrfs send stderr: %s", err)
	}

	// Handle errors.
	errs := []error{}
	chStdoutPipeErr := <-chStdoutPipe

	err = cmd.Wait()
	if err != nil {
		errs = append(errs, err)

		if chStdoutPipeErr != nil {
			errs = append(errs, chStdoutPipeErr)
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("Btrfs send failed: %v (%s)", errs, string(output))
	}

	return nil
}

func (d *btrfs) receiveSubvolume(path string, targetPath string, conn io.ReadWriteCloser, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
	// Assemble btrfs send command.
	cmd := exec.Command("btrfs", "receive", "-e", path)

	// Prepare stdin/stderr.
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	// Forward input through stdin.
	chCopyConn := make(chan error, 1)
	go func() {
		_, err = io.Copy(stdin, conn)
		stdin.Close()
		chCopyConn <- err
	}()

	// Run the command.
	err = cmd.Start()
	if err != nil {
		return err
	}

	// Read any error.
	output, err := ioutil.ReadAll(stderr)
	if err != nil {
		logger.Debugf("Problem reading btrfs receive stderr %s", err)
	}

	// Handle errors.
	errs := []error{}
	chCopyConnErr := <-chCopyConn

	err = cmd.Wait()
	if err != nil {
		errs = append(errs, err)

		if chCopyConnErr != nil {
			errs = append(errs, chCopyConnErr)
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("Problem with btrfs receive: (%v) %s", errs, string(output))
	}

	// If we receive and target paths match, we're done.
	if path == targetPath {
		return nil
	}

	// Handle older LXD versions.
	receivedSnapshot := fmt.Sprintf("%s/.migration-send", path)
	if !shared.PathExists(receivedSnapshot) {
		receivedSnapshot = fmt.Sprintf("%s/.root", path)
	}

	// Mark the received subvolume writable.
	_, err = shared.RunCommand("btrfs", "property", "set", "-ts", receivedSnapshot, "ro", "false")
	if err != nil {
		return err
	}

	// And move it to the target path.
	err = os.Rename(receivedSnapshot, targetPath)
	if err != nil {
		return errors.Wrapf(err, "Failed to rename '%s' to '%s'", receivedSnapshot, targetPath)
	}

	return nil
}

// volumeSize returns the size to use when creating new a volume.
func (d *btrfs) volumeSize(vol Volume) string {
	size := vol.ExpandedConfig("size")

	// Block images always need a size.
	if vol.contentType == ContentTypeBlock && (size == "" || size == "0") {
		return defaultBlockSize
	}

	return size
}

// setSubvolumeReadonlyProperty sets the readonly property on the subvolume to true or false.
func (d *btrfs) setSubvolumeReadonlyProperty(path string, readonly bool) error {
	// Silently ignore requests to set subvolume readonly property if running in a user namespace as we won't
	// be able to change it if it is readonly already, and making it readonly will mean we cannot undo it.
	if d.state.OS.RunningInUserNS {
		return nil
	}

	_, err := shared.RunCommand("btrfs", "property", "set", "-ts", path, "ro", fmt.Sprintf("%t", readonly))
	return err
}

// BTRFSSubVolume is the structure used to store information about a subvolume.
type BTRFSSubVolume struct {
	Path     string // Path inside the volume where the subvolume belongs (so / is the top of the volume tree).
	Snapshot string // Snapshot name the subvolume belongs to.
	Readonly bool   // Is the sub volume read only or not.
}

// getSubvolumesMetaData retrieves subvolume meta data with paths relative to the root volume.
// The first item in the returned list is the root subvolume itself.
func (d *btrfs) getSubvolumesMetaData(vol Volume) ([]BTRFSSubVolume, error) {
	var subVols []BTRFSSubVolume

	snapName := ""
	if vol.IsSnapshot() {
		_, snapName, _ = shared.InstanceGetParentAndSnapshotName(vol.name)
	}

	// Add main root volume to subvolumes list first.
	subVols = append(subVols, BTRFSSubVolume{
		Snapshot: snapName,
		Path:     string(filepath.Separator),
		Readonly: BTRFSSubVolumeIsRo(vol.MountPath()),
	})

	// Find any subvolumes in volume.
	subVolPaths, err := d.getSubvolumes(vol.MountPath())
	if err != nil {
		return nil, err
	}
	sort.Sort(sort.StringSlice(subVolPaths))

	// Add any subvolumes under the root subvolume with relative path to root.
	for _, subVolPath := range subVolPaths {
		subVols = append(subVols, BTRFSSubVolume{
			Snapshot: snapName,
			Path:     fmt.Sprintf("%s%s", string(filepath.Separator), subVolPath),
			Readonly: BTRFSSubVolumeIsRo(filepath.Join(vol.MountPath(), subVolPath)),
		})
	}

	return subVols, nil
}
<commit_msg>lxd/storage/driversr/driver/btrfs/utils: Allow ro subvolumes to be deleted in deleteSubvolume<commit_after>package drivers

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	"golang.org/x/sys/unix"

	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/ioprogress"
	"github.com/lxc/lxd/shared/logger"
)

// Errors
var errBtrfsNoQuota = fmt.Errorf("Quotas disabled on filesystem")
var errBtrfsNoQGroup = fmt.Errorf("Unable to find quota group")

func (d *btrfs) getMountOptions() string {
	// Allow overriding the default options.
	if d.config["btrfs.mount_options"] != "" {
		return d.config["btrfs.mount_options"]
	}

	return "user_subvol_rm_allowed"
}

func (d *btrfs) isSubvolume(path string) bool {
	// Stat the path.
	fs := unix.Stat_t{}
	err := unix.Lstat(path, &fs)
	if err != nil {
		return false
	}

	// Check if BTRFS_FIRST_FREE_OBJECTID is the inode number.
	if fs.Ino != 256 {
		return false
	}

	return true
}

func (d *btrfs) getSubvolumes(path string) ([]string, error) {
	result := []string{}

	// Make sure the path has a trailing slash.
	if !strings.HasSuffix(path, "/") {
		path = path + "/"
	}

	// Walk through the entire tree looking for subvolumes.
	err := filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Ignore the base path.
		if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
			return nil
		}

		// Subvolumes can only be directories.
		if !fi.IsDir() {
			return nil
		}

		// Check if a subvolume.
		if d.isSubvolume(fpath) {
			result = append(result, strings.TrimPrefix(fpath, path))
		}

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

	return result, nil
}

// snapshotSubvolume creates a snapshot of the specified path at the dest supplied. If recursion is true and
// sub volumes are found below the path then they are created at the relative location in dest.
func (d *btrfs) snapshotSubvolume(path string, dest string, recursion bool) error {
	// Single subvolume deletion.
	snapshot := func(path string, dest string) error {
		_, err := shared.RunCommand("btrfs", "subvolume", "snapshot", path, dest)
		if err != nil {
			return err
		}

		return nil
	}

	// First snapshot the root.
	err := snapshot(path, dest)
	if err != nil {
		return err
	}

	// Now snapshot all subvolumes of the root.
	if recursion {
		// Get the subvolumes list.
		subSubVols, err := d.getSubvolumes(path)
		if err != nil {
			return err
		}
		sort.Sort(sort.StringSlice(subSubVols))

		for _, subSubVol := range subSubVols {
			subSubVolSnapPath := filepath.Join(dest, subSubVol)

			// Clear the target for the subvol to use.
			os.Remove(subSubVolSnapPath)

			err := snapshot(filepath.Join(path, subSubVol), subSubVolSnapPath)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (d *btrfs) deleteSubvolume(path string, recursion bool) error {
	// Single subvolume deletion.
	destroy := func(path string) error {
		// Attempt (but don't fail on) to delete any qgroup on the subvolume.
		qgroup, _, err := d.getQGroup(path)
		if err == nil {
			shared.RunCommand("btrfs", "qgroup", "destroy", qgroup, path)
		}

		// Attempt to make the subvolume writable.
		d.setSubvolumeReadonlyProperty(path, false)

		// Temporarily change ownership & mode to help with nesting.
		os.Chmod(path, 0700)
		os.Chown(path, 0, 0)

		// Delete the subvolume itself.
		_, err = shared.RunCommand("btrfs", "subvolume", "delete", path)

		return err
	}

	// Delete subsubvols.
	if recursion {
		// Get the subvolumes list.
		subsubvols, err := d.getSubvolumes(path)
		if err != nil {
			return err
		}
		sort.Sort(sort.Reverse(sort.StringSlice(subsubvols)))

		if len(subsubvols) > 0 {
			// Attempt to make the root subvolume writable so any subvolumes can be removed.
			d.setSubvolumeReadonlyProperty(path, false)
		}

		for _, subsubvol := range subsubvols {
			err := destroy(filepath.Join(path, subsubvol))
			if err != nil {
				return err
			}
		}
	}

	// Delete the subvol itself.
	err := destroy(path)
	if err != nil {
		return err
	}

	return nil
}

func (d *btrfs) getQGroup(path string) (string, int64, error) {
	// Try to get the qgroup details.
	output, err := shared.RunCommand("btrfs", "qgroup", "show", "-e", "-f", "--raw", path)
	if err != nil {
		return "", -1, errBtrfsNoQuota
	}

	// Parse to extract the qgroup identifier.
	var qgroup string
	usage := int64(-1)
	for _, line := range strings.Split(output, "\n") {
		if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
			continue
		}

		fields := strings.Fields(line)
		if len(fields) != 4 {
			continue
		}

		qgroup = fields[0]
		val, err := strconv.ParseInt(fields[2], 10, 64)
		if err == nil {
			usage = val
		}

		break
	}

	if qgroup == "" {
		return "", -1, errBtrfsNoQGroup
	}

	return qgroup, usage, nil
}

func (d *btrfs) sendSubvolume(path string, parent string, conn io.ReadWriteCloser, tracker *ioprogress.ProgressTracker) error {
	// Assemble btrfs send command.
	args := []string{"send"}
	if parent != "" {
		args = append(args, "-p", parent)
	}
	args = append(args, path)
	cmd := exec.Command("btrfs", args...)

	// Prepare stdout/stderr.
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	// Setup progress tracker.
	stdoutPipe := stdout
	if tracker != nil {
		stdoutPipe = &ioprogress.ProgressReader{
			ReadCloser: stdout,
			Tracker:    tracker,
		}
	}

	// Forward any output on stdout.
	chStdoutPipe := make(chan error, 1)
	go func() {
		_, err := io.Copy(conn, stdoutPipe)
		chStdoutPipe <- err
		conn.Close()
	}()

	// Run the command.
	err = cmd.Start()
	if err != nil {
		return err
	}

	// Read any error.
	output, err := ioutil.ReadAll(stderr)
	if err != nil {
		logger.Errorf("Problem reading btrfs send stderr: %s", err)
	}

	// Handle errors.
	errs := []error{}
	chStdoutPipeErr := <-chStdoutPipe

	err = cmd.Wait()
	if err != nil {
		errs = append(errs, err)

		if chStdoutPipeErr != nil {
			errs = append(errs, chStdoutPipeErr)
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("Btrfs send failed: %v (%s)", errs, string(output))
	}

	return nil
}

func (d *btrfs) receiveSubvolume(path string, targetPath string, conn io.ReadWriteCloser, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
	// Assemble btrfs send command.
	cmd := exec.Command("btrfs", "receive", "-e", path)

	// Prepare stdin/stderr.
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	// Forward input through stdin.
	chCopyConn := make(chan error, 1)
	go func() {
		_, err = io.Copy(stdin, conn)
		stdin.Close()
		chCopyConn <- err
	}()

	// Run the command.
	err = cmd.Start()
	if err != nil {
		return err
	}

	// Read any error.
	output, err := ioutil.ReadAll(stderr)
	if err != nil {
		logger.Debugf("Problem reading btrfs receive stderr %s", err)
	}

	// Handle errors.
	errs := []error{}
	chCopyConnErr := <-chCopyConn

	err = cmd.Wait()
	if err != nil {
		errs = append(errs, err)

		if chCopyConnErr != nil {
			errs = append(errs, chCopyConnErr)
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("Problem with btrfs receive: (%v) %s", errs, string(output))
	}

	// If we receive and target paths match, we're done.
	if path == targetPath {
		return nil
	}

	// Handle older LXD versions.
	receivedSnapshot := fmt.Sprintf("%s/.migration-send", path)
	if !shared.PathExists(receivedSnapshot) {
		receivedSnapshot = fmt.Sprintf("%s/.root", path)
	}

	// Mark the received subvolume writable.
	_, err = shared.RunCommand("btrfs", "property", "set", "-ts", receivedSnapshot, "ro", "false")
	if err != nil {
		return err
	}

	// And move it to the target path.
	err = os.Rename(receivedSnapshot, targetPath)
	if err != nil {
		return errors.Wrapf(err, "Failed to rename '%s' to '%s'", receivedSnapshot, targetPath)
	}

	return nil
}

// volumeSize returns the size to use when creating new a volume.
func (d *btrfs) volumeSize(vol Volume) string {
	size := vol.ExpandedConfig("size")

	// Block images always need a size.
	if vol.contentType == ContentTypeBlock && (size == "" || size == "0") {
		return defaultBlockSize
	}

	return size
}

// setSubvolumeReadonlyProperty sets the readonly property on the subvolume to true or false.
func (d *btrfs) setSubvolumeReadonlyProperty(path string, readonly bool) error {
	// Silently ignore requests to set subvolume readonly property if running in a user namespace as we won't
	// be able to change it if it is readonly already, and making it readonly will mean we cannot undo it.
	if d.state.OS.RunningInUserNS {
		return nil
	}

	_, err := shared.RunCommand("btrfs", "property", "set", "-ts", path, "ro", fmt.Sprintf("%t", readonly))
	return err
}

// BTRFSSubVolume is the structure used to store information about a subvolume.
type BTRFSSubVolume struct {
	Path     string // Path inside the volume where the subvolume belongs (so / is the top of the volume tree).
	Snapshot string // Snapshot name the subvolume belongs to.
	Readonly bool   // Is the sub volume read only or not.
}

// getSubvolumesMetaData retrieves subvolume meta data with paths relative to the root volume.
// The first item in the returned list is the root subvolume itself.
func (d *btrfs) getSubvolumesMetaData(vol Volume) ([]BTRFSSubVolume, error) {
	var subVols []BTRFSSubVolume

	snapName := ""
	if vol.IsSnapshot() {
		_, snapName, _ = shared.InstanceGetParentAndSnapshotName(vol.name)
	}

	// Add main root volume to subvolumes list first.
	subVols = append(subVols, BTRFSSubVolume{
		Snapshot: snapName,
		Path:     string(filepath.Separator),
		Readonly: BTRFSSubVolumeIsRo(vol.MountPath()),
	})

	// Find any subvolumes in volume.
	subVolPaths, err := d.getSubvolumes(vol.MountPath())
	if err != nil {
		return nil, err
	}
	sort.Sort(sort.StringSlice(subVolPaths))

	// Add any subvolumes under the root subvolume with relative path to root.
	for _, subVolPath := range subVolPaths {
		subVols = append(subVols, BTRFSSubVolume{
			Snapshot: snapName,
			Path:     fmt.Sprintf("%s%s", string(filepath.Separator), subVolPath),
			Readonly: BTRFSSubVolumeIsRo(filepath.Join(vol.MountPath(), subVolPath)),
		})
	}

	return subVols, nil
}
<|endoftext|>


<commit_before>package db_test

import (
	"log"
	"os"
	"testing"
	"time"

	"github.com/syou6162/go-active-learning/lib/db"
	"github.com/syou6162/go-active-learning/lib/example"
)

func TestMain(m *testing.M) {
	err := db.Init()
	if err != nil {
		log.Fatal(err.Error())
	}
	defer db.Close()

	ret := m.Run()
	os.Exit(ret)
}

func TestPing(t *testing.T) {
	if err := db.Ping(); err != nil {
		t.Errorf(err.Error())
	}
}

func TestInsertExamplesFromReader(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	fp, err := os.Open("../../tech_input_example.txt")
	defer fp.Close()
	if err != nil {
		t.Error(err)
	}
	db.InsertExamplesFromReader(fp)

	examples, err := db.ReadExamples()
	if err != nil {
		t.Error(err)
	}
	if len(examples) == 0 {
		t.Errorf("len(examples) > 0, but %d", len(examples))
	}
}

func TestInsertOrUpdateExample(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}

	examples, err := db.ReadExamples()
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 1 {
		t.Errorf("len(examples) == %d, want 1", len(examples))
	}

	// same url
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}

	examples, err = db.ReadExamples()
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 1 {
		t.Errorf("len(examples) == %d, want 1", len(examples))
	}

	// different url
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://another.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}

	examples, err = db.ReadExamples()
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 2 {
		t.Errorf("len(examples) == %d, want 2", len(examples))
	}
}

func TestReadLabeledExamples(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge1.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge2.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge3.com", Label: example.UNLABELED})
	if err != nil {
		t.Error(err)
	}

	examples, err := db.ReadLabeledExamples(10)
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 2 {
		t.Errorf("len(examples) == %d, want 2", len(examples))
	}
}

func TestReadRecentExamples(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge1.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge2.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge3.com", Label: example.UNLABELED})
	if err != nil {
		t.Error(err)
	}

	examples, err := db.ReadRecentExamples(time.Now().Add(time.Duration(-10) * time.Minute))
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 3 {
		t.Errorf("len(examples) == %d, want 3", len(examples))
	}
}
<commit_msg>SearchExamplesByUlrsのテストを追加<commit_after>package db_test

import (
	"log"
	"os"
	"testing"
	"time"

	"github.com/syou6162/go-active-learning/lib/db"
	"github.com/syou6162/go-active-learning/lib/example"
)

func TestMain(m *testing.M) {
	err := db.Init()
	if err != nil {
		log.Fatal(err.Error())
	}
	defer db.Close()

	ret := m.Run()
	os.Exit(ret)
}

func TestPing(t *testing.T) {
	if err := db.Ping(); err != nil {
		t.Errorf(err.Error())
	}
}

func TestInsertExamplesFromReader(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	fp, err := os.Open("../../tech_input_example.txt")
	defer fp.Close()
	if err != nil {
		t.Error(err)
	}
	db.InsertExamplesFromReader(fp)

	examples, err := db.ReadExamples()
	if err != nil {
		t.Error(err)
	}
	if len(examples) == 0 {
		t.Errorf("len(examples) > 0, but %d", len(examples))
	}
}

func TestInsertOrUpdateExample(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}

	examples, err := db.ReadExamples()
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 1 {
		t.Errorf("len(examples) == %d, want 1", len(examples))
	}

	// same url
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}

	examples, err = db.ReadExamples()
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 1 {
		t.Errorf("len(examples) == %d, want 1", len(examples))
	}

	// different url
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://another.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}

	examples, err = db.ReadExamples()
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 2 {
		t.Errorf("len(examples) == %d, want 2", len(examples))
	}
}

func TestReadLabeledExamples(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge1.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge2.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge3.com", Label: example.UNLABELED})
	if err != nil {
		t.Error(err)
	}

	examples, err := db.ReadLabeledExamples(10)
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 2 {
		t.Errorf("len(examples) == %d, want 2", len(examples))
	}
}

func TestReadRecentExamples(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge1.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge2.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge3.com", Label: example.UNLABELED})
	if err != nil {
		t.Error(err)
	}

	examples, err := db.ReadRecentExamples(time.Now().Add(time.Duration(-10) * time.Minute))
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 3 {
		t.Errorf("len(examples) == %d, want 3", len(examples))
	}
}

func TestSearchExamplesByUlrs(t *testing.T) {
	_, err := db.DeleteAllExamples()
	if err != nil {
		t.Error(err)
	}

	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge1.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge2.com", Label: example.NEGATIVE})
	if err != nil {
		t.Error(err)
	}
	_, err = db.InsertOrUpdateExample(&example.Example{Url: "http://hoge3.com", Label: example.UNLABELED})
	if err != nil {
		t.Error(err)
	}

	examples, err := db.SearchExamplesByUlrs([]string{"http://hoge1.com", "http://hoge2.com"})
	if err != nil {
		t.Error(err)
	}
	if len(examples) != 2 {
		t.Errorf("len(examples) == %d, want 2", len(examples))
	}
}
<|endoftext|>


<commit_before>package syntax

import (
	"regexp"
	"unicode/utf8"

	termbox "github.com/nsf/termbox-go"
)

func init() {
	Languages["go"] = Language{
		Syntax{"string", regexp.MustCompile(`^(?m)".*?(?:[^\\]"|$)`), termbox.ColorRed, termbox.ColorBlack},
		Syntax{"raw string", regexp.MustCompile(`^(?s)` + "`" + `.*?` + "(?:`|$)"), termbox.ColorRed, termbox.ColorBlack},
		Syntax{"rune", regexp.MustCompile(`^(?m)'.*?(?:[^\\]'|$)`), termbox.ColorYellow, termbox.ColorBlack},
		Syntax{"comment", regexp.MustCompile(`^(?m)//.*`), termbox.ColorMagenta, termbox.ColorBlack},
		Syntax{"multi line comment", regexp.MustCompile(`^(?s)/[*].*?(?:[*]/|$)`), termbox.ColorMagenta, termbox.ColorBlack},
		Syntax{"trailing spaces", regexp.MustCompile(`^(?m)[ \t]+$`), termbox.ColorBlack, termbox.ColorYellow},
		Syntax{"package", regexp.MustCompile(`^package\s`), termbox.ColorYellow, termbox.ColorBlack},
	}

	Languages["py"] = Language{
		Syntax{"multi line string1", regexp.MustCompile(`^(?s)""".*?(?:"""|$)`), termbox.ColorRed, termbox.ColorBlack},
		Syntax{"multi line string2", regexp.MustCompile(`^(?s)'''.*?(?:'''|$)`), termbox.ColorYellow, termbox.ColorBlack},
		Syntax{"string1", regexp.MustCompile(`^(?m)".*?(?:[^\\]"|$)`), termbox.ColorRed, termbox.ColorBlack},
		Syntax{"string2", regexp.MustCompile(`^(?m)'.*?(?:[^\\]'|$)`), termbox.ColorYellow, termbox.ColorBlack},
		Syntax{"comment", regexp.MustCompile(`^(?m)#.*`), termbox.ColorMagenta, termbox.ColorBlack},
		Syntax{"trailing spaces", regexp.MustCompile(`^(?m)[ \t]+$`), termbox.ColorBlack, termbox.ColorYellow},
	}
}

type Syntax struct {
	Name string
	Re   *regexp.Regexp
	Fg   termbox.Attribute
	Bg   termbox.Attribute
}

func (s Syntax) NewMatch(start, end Pos) Match {
	return Match{Name: s.Name, Start: start, End: end, Fg: s.Fg, Bg: s.Bg}
}

type Language []Syntax

var Languages = make(map[string]Language)

func (l Language) Parse(text []byte) []Match {
	c := NewCursor(text)
	matches := []Match{}
Loop:
	for {
		for _, syn := range l {
			ms := syn.Re.FindSubmatch(c.Remain())
			if ms == nil {
				continue
			}
			m := ms[0]
			if len(ms) == 2 {
				m = ms[1]
			}
			if string(m) == "" {
				continue
			}
			start := c.Pos()
			c.Skip(len(m))
			end := c.Pos()
			matches = append(matches, syn.NewMatch(start, end))
			continue Loop
		}
		if !c.Advance() {
			break
		}
	}
	return matches
}

// ParseRange checks and replace matches from min to max.
// When there is an overwrap between old matches and min Pos,
// it will recaculate matches from the overwrap begins.
func (l Language) ParseRange(matches []Match, text []byte, min, max Pos) []Match {
	// check where parse acutally started.
	last := -1
	overwrap := false
	for i, m := range matches {
		if m.Min().Compare(min) < 0 {
			if m.Max().Compare(min) < 0 {
				continue
			}
			overwrap = true
			last = i
			break
		}
		last = i
		break
	}

	parseStart := min
	if last != -1 {
		if overwrap {
			parseStart = matches[last].Min()
		}
		matches = matches[:last]
	}

	// move cursor to start position.
	c := NewCursor(text)
	for c.Pos().Compare(parseStart) < 0 {
		ok := c.Advance()
		if !ok {
			// already end of text. nothing to do.
			return matches
		}
	}

Loop:
	for {
		if c.Pos().Compare(max) >= 0 {
			break
		}
		for _, syn := range l {
			ms := syn.Re.FindSubmatch(c.Remain())
			if ms == nil {
				continue
			}
			m := ms[0]
			if len(ms) == 2 {
				m = ms[1]
			}
			if string(m) == "" {
				continue
			}
			start := c.Pos()
			c.Skip(len(m))
			end := c.Pos()
			matches = append(matches, syn.NewMatch(start, end))
			continue Loop
		}
		if !c.Advance() {
			break
		}
	}
	return matches
}

type Cursor struct {
	text []byte
	b    int // byte offset
	l    int // line offset
	o    int // byte in line offset
}

func NewCursor(text []byte) *Cursor {
	return &Cursor{text: text}
}

func (c *Cursor) Pos() Pos {
	return Pos{c.l, c.o}
}

func (c *Cursor) Remain() []byte {
	if c.l == len(c.text) {
		return []byte("")
	}
	return c.text[c.b:]
}

func (c *Cursor) Advance() bool {
	if c.b == len(c.text) {
		return false
	}
	c.next()
	return true
}

func (c *Cursor) Skip(b int) {
	i := 0
	for i < b {
		_, size := c.next()
		i += size
	}
}

func (c *Cursor) next() (r rune, size int) {
	r, size = utf8.DecodeRune(c.Remain())
	c.b += size
	c.o += size
	if r == '\n' {
		c.l += 1
		c.o = 0
	}
	return r, size
}

type Match struct {
	Name  string
	Start Pos
	End   Pos
	Fg    termbox.Attribute
	Bg    termbox.Attribute
}

func (m *Match) MinMax() (Pos, Pos) {
	if m.Start.L < m.End.L {
		return m.Start, m.End
	}
	if m.Start.L == m.End.L && m.Start.O <= m.End.O {
		return m.Start, m.End
	}
	return m.End, m.Start
}

func (m *Match) Min() Pos {
	min, _ := m.MinMax()
	return min
}

func (m *Match) Max() Pos {
	_, max := m.MinMax()
	return max
}

type Pos struct {
	L int
	O int
}

// Compare compares two Pos a and b.
// If a < b, it will return -1.
// If a > b, it will return 1.
// If a == b, it will return 0
func (a Pos) Compare(b Pos) int {
	if a.L < b.L {
		return -1
	}
	if a.L == b.L {
		if a.O < b.O {
			return -1
		}
		if a.O == b.O {
			return 0
		}
		return 1
	}
	return 1
}
<commit_msg>syntax: fix all string-like Syntax to parse empty string<commit_after>package syntax

import (
	"regexp"
	"unicode/utf8"

	termbox "github.com/nsf/termbox-go"
)

func init() {
	Languages["go"] = Language{
		Syntax{"string", regexp.MustCompile(`^(?m)".*?(?:[^\\]?"|$)`), termbox.ColorRed, termbox.ColorBlack},
		Syntax{"raw string", regexp.MustCompile(`^(?s)` + "`" + `.*?` + "(?:`|$)"), termbox.ColorRed, termbox.ColorBlack},
		Syntax{"rune", regexp.MustCompile(`^(?m)'.*?(?:[^\\]?'|$)`), termbox.ColorYellow, termbox.ColorBlack},
		Syntax{"comment", regexp.MustCompile(`^(?m)//.*`), termbox.ColorMagenta, termbox.ColorBlack},
		Syntax{"multi line comment", regexp.MustCompile(`^(?s)/[*].*?(?:[*]/|$)`), termbox.ColorMagenta, termbox.ColorBlack},
		Syntax{"trailing spaces", regexp.MustCompile(`^(?m)[ \t]+$`), termbox.ColorBlack, termbox.ColorYellow},
		Syntax{"package", regexp.MustCompile(`^package\s`), termbox.ColorYellow, termbox.ColorBlack},
	}

	Languages["py"] = Language{
		Syntax{"multi line string1", regexp.MustCompile(`^(?s)""".*?(?:"""|$)`), termbox.ColorRed, termbox.ColorBlack},
		Syntax{"multi line string2", regexp.MustCompile(`^(?s)'''.*?(?:'''|$)`), termbox.ColorYellow, termbox.ColorBlack},
		Syntax{"string1", regexp.MustCompile(`^(?m)".*?(?:[^\\]?"|$)`), termbox.ColorRed, termbox.ColorBlack},
		Syntax{"string2", regexp.MustCompile(`^(?m)'.*?(?:[^\\]?'|$)`), termbox.ColorYellow, termbox.ColorBlack},
		Syntax{"comment", regexp.MustCompile(`^(?m)#.*`), termbox.ColorMagenta, termbox.ColorBlack},
		Syntax{"trailing spaces", regexp.MustCompile(`^(?m)[ \t]+$`), termbox.ColorBlack, termbox.ColorYellow},
	}
}

type Syntax struct {
	Name string
	Re   *regexp.Regexp
	Fg   termbox.Attribute
	Bg   termbox.Attribute
}

func (s Syntax) NewMatch(start, end Pos) Match {
	return Match{Name: s.Name, Start: start, End: end, Fg: s.Fg, Bg: s.Bg}
}

type Language []Syntax

var Languages = make(map[string]Language)

func (l Language) Parse(text []byte) []Match {
	c := NewCursor(text)
	matches := []Match{}
Loop:
	for {
		for _, syn := range l {
			ms := syn.Re.FindSubmatch(c.Remain())
			if ms == nil {
				continue
			}
			m := ms[0]
			if len(ms) == 2 {
				m = ms[1]
			}
			if string(m) == "" {
				continue
			}
			start := c.Pos()
			c.Skip(len(m))
			end := c.Pos()
			matches = append(matches, syn.NewMatch(start, end))
			continue Loop
		}
		if !c.Advance() {
			break
		}
	}
	return matches
}

// ParseRange checks and replace matches from min to max.
// When there is an overwrap between old matches and min Pos,
// it will recaculate matches from the overwrap begins.
func (l Language) ParseRange(matches []Match, text []byte, min, max Pos) []Match {
	// check where parse acutally started.
	last := -1
	overwrap := false
	for i, m := range matches {
		if m.Min().Compare(min) < 0 {
			if m.Max().Compare(min) < 0 {
				continue
			}
			overwrap = true
			last = i
			break
		}
		last = i
		break
	}

	parseStart := min
	if last != -1 {
		if overwrap {
			parseStart = matches[last].Min()
		}
		matches = matches[:last]
	}

	// move cursor to start position.
	c := NewCursor(text)
	for c.Pos().Compare(parseStart) < 0 {
		ok := c.Advance()
		if !ok {
			// already end of text. nothing to do.
			return matches
		}
	}

Loop:
	for {
		if c.Pos().Compare(max) >= 0 {
			break
		}
		for _, syn := range l {
			ms := syn.Re.FindSubmatch(c.Remain())
			if ms == nil {
				continue
			}
			m := ms[0]
			if len(ms) == 2 {
				m = ms[1]
			}
			if string(m) == "" {
				continue
			}
			start := c.Pos()
			c.Skip(len(m))
			end := c.Pos()
			matches = append(matches, syn.NewMatch(start, end))
			continue Loop
		}
		if !c.Advance() {
			break
		}
	}
	return matches
}

type Cursor struct {
	text []byte
	b    int // byte offset
	l    int // line offset
	o    int // byte in line offset
}

func NewCursor(text []byte) *Cursor {
	return &Cursor{text: text}
}

func (c *Cursor) Pos() Pos {
	return Pos{c.l, c.o}
}

func (c *Cursor) Remain() []byte {
	if c.l == len(c.text) {
		return []byte("")
	}
	return c.text[c.b:]
}

func (c *Cursor) Advance() bool {
	if c.b == len(c.text) {
		return false
	}
	c.next()
	return true
}

func (c *Cursor) Skip(b int) {
	i := 0
	for i < b {
		_, size := c.next()
		i += size
	}
}

func (c *Cursor) next() (r rune, size int) {
	r, size = utf8.DecodeRune(c.Remain())
	c.b += size
	c.o += size
	if r == '\n' {
		c.l += 1
		c.o = 0
	}
	return r, size
}

type Match struct {
	Name  string
	Start Pos
	End   Pos
	Fg    termbox.Attribute
	Bg    termbox.Attribute
}

func (m *Match) MinMax() (Pos, Pos) {
	if m.Start.L < m.End.L {
		return m.Start, m.End
	}
	if m.Start.L == m.End.L && m.Start.O <= m.End.O {
		return m.Start, m.End
	}
	return m.End, m.Start
}

func (m *Match) Min() Pos {
	min, _ := m.MinMax()
	return min
}

func (m *Match) Max() Pos {
	_, max := m.MinMax()
	return max
}

type Pos struct {
	L int
	O int
}

// Compare compares two Pos a and b.
// If a < b, it will return -1.
// If a > b, it will return 1.
// If a == b, it will return 0
func (a Pos) Compare(b Pos) int {
	if a.L < b.L {
		return -1
	}
	if a.L == b.L {
		if a.O < b.O {
			return -1
		}
		if a.O == b.O {
			return 0
		}
		return 1
	}
	return 1
}
<|endoftext|>


<commit_before>package systree

import (
	"strconv"
	"sync/atomic"
	"time"

	"github.com/VolantMQ/vlapi/mqttp"
)

// DynamicValue interface describes states of the dynamic value
type DynamicValue interface {
	Topic() string
	// Retained used by topics provider to get retained message when there is new subscription to given topic
	Retained() *mqttp.Publish
	// Publish used by systree update routine to publish new value when on periodic basis
	Publish() *mqttp.Publish
}

type dynamicValue struct {
	topic    string
	retained *mqttp.Publish
	publish  *mqttp.Publish
	getValue func() []byte
}

type dynamicValueInteger struct {
	dynamicValue
	val uint64
}

type dynamicValueUpTime struct {
	dynamicValue
	startTime time.Time
}

type dynamicValueCurrentTime struct {
	dynamicValue
}

func newDynamicValueInteger(topic string) *dynamicValueInteger {
	v := &dynamicValueInteger{}
	v.topic = topic
	v.getValue = v.get

	return v
}

func newDynamicValueUpTime(topic string) *dynamicValueUpTime {
	v := &dynamicValueUpTime{
		startTime: time.Now(),
	}

	v.topic = topic
	v.getValue = v.get

	return v
}

func newDynamicValueCurrentTime(topic string) *dynamicValueCurrentTime {
	v := &dynamicValueCurrentTime{}
	v.topic = topic
	v.getValue = v.get

	return v
}

func (v *dynamicValueInteger) get() []byte {
	val := strconv.FormatUint(atomic.LoadUint64(&v.val), 10)
	return []byte(val)
}

func (v *dynamicValueUpTime) get() []byte {
	diff := time.Since(v.startTime)

	return []byte(diff.String())
}

func (v *dynamicValueCurrentTime) get() []byte {
	val := time.Now().Format(time.RFC3339)
	return []byte(val)
}

func (m *dynamicValue) Topic() string {
	return m.topic
}

func (m *dynamicValue) Retained() *mqttp.Publish {
	if m.retained == nil {
		np, _ := mqttp.New(mqttp.ProtocolV311, mqttp.PUBLISH)
		m.retained, _ = np.(*mqttp.Publish)
		m.retained.SetTopic(m.topic)  // nolint: errcheck
		m.retained.SetQoS(mqttp.QoS0) // nolint: errcheck
		m.retained.SetRetain(true)
	}

	m.retained.SetPayload(m.getValue())

	return m.retained
}

func (m *dynamicValue) Publish() *mqttp.Publish {
	if m.publish == nil {
		np, _ := mqttp.New(mqttp.ProtocolV311, mqttp.PUBLISH)
		m.publish, _ = np.(*mqttp.Publish)
		m.publish.SetTopic(m.topic)  // nolint: errcheck
		m.publish.SetQoS(mqttp.QoS0) // nolint: errcheck
		m.publish.SetRetain(true)
	}

	m.publish.SetPayload(m.getValue())

	return m.publish
}
<commit_msg>atomic uint64 fix for arm<commit_after>package systree

import (
	"strconv"
	"sync/atomic"
	"time"

	"github.com/VolantMQ/vlapi/mqttp"
)

// DynamicValue interface describes states of the dynamic value
type DynamicValue interface {
	Topic() string
	// Retained used by topics provider to get retained message when there is new subscription to given topic
	Retained() *mqttp.Publish
	// Publish used by systree update routine to publish new value when on periodic basis
	Publish() *mqttp.Publish
}

type dynamicValue struct {
	topic    string
	retained *mqttp.Publish
	publish  *mqttp.Publish
	getValue func() []byte
}

type dynamicValueInteger struct {
	val uint64
	dynamicValue
}

type dynamicValueUpTime struct {
	dynamicValue
	startTime time.Time
}

type dynamicValueCurrentTime struct {
	dynamicValue
}

func newDynamicValueInteger(topic string) *dynamicValueInteger {
	v := &dynamicValueInteger{}
	v.topic = topic
	v.getValue = v.get

	return v
}

func newDynamicValueUpTime(topic string) *dynamicValueUpTime {
	v := &dynamicValueUpTime{
		startTime: time.Now(),
	}

	v.topic = topic
	v.getValue = v.get

	return v
}

func newDynamicValueCurrentTime(topic string) *dynamicValueCurrentTime {
	v := &dynamicValueCurrentTime{}
	v.topic = topic
	v.getValue = v.get

	return v
}

func (v *dynamicValueInteger) get() []byte {
	val := strconv.FormatUint(atomic.LoadUint64(&v.val), 10)
	return []byte(val)
}

func (v *dynamicValueUpTime) get() []byte {
	diff := time.Since(v.startTime)

	return []byte(diff.String())
}

func (v *dynamicValueCurrentTime) get() []byte {
	val := time.Now().Format(time.RFC3339)
	return []byte(val)
}

func (m *dynamicValue) Topic() string {
	return m.topic
}

func (m *dynamicValue) Retained() *mqttp.Publish {
	if m.retained == nil {
		np, _ := mqttp.New(mqttp.ProtocolV311, mqttp.PUBLISH)
		m.retained, _ = np.(*mqttp.Publish)
		m.retained.SetTopic(m.topic)  // nolint: errcheck
		m.retained.SetQoS(mqttp.QoS0) // nolint: errcheck
		m.retained.SetRetain(true)
	}

	m.retained.SetPayload(m.getValue())

	return m.retained
}

func (m *dynamicValue) Publish() *mqttp.Publish {
	if m.publish == nil {
		np, _ := mqttp.New(mqttp.ProtocolV311, mqttp.PUBLISH)
		m.publish, _ = np.(*mqttp.Publish)
		m.publish.SetTopic(m.topic)  // nolint: errcheck
		m.publish.SetQoS(mqttp.QoS0) // nolint: errcheck
		m.publish.SetRetain(true)
	}

	m.publish.SetPayload(m.getValue())

	return m.publish
}
<|endoftext|>


<commit_before>package mpmulticore

import (
	"bufio"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	mp "github.com/mackerelio/go-mackerel-plugin"
)

var graphDef = map[string]mp.Graphs{
	"multicore.cpu.#": {
		Label: "MultiCore CPU",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "guest_nice", Label: "guest_nice", Diff: false, Stacked: true},
			{Name: "guest", Label: "guest", Diff: false, Stacked: true},
			{Name: "steal", Label: "steal", Diff: false, Stacked: true},
			{Name: "softirq", Label: "softirq", Diff: false, Stacked: true},
			{Name: "irq", Label: "irq", Diff: false, Stacked: true},
			{Name: "iowait", Label: "ioWait", Diff: false, Stacked: true},
			{Name: "idle", Label: "idle", Diff: false, Stacked: true},
			{Name: "system", Label: "system", Diff: false, Stacked: true},
			{Name: "nice", Label: "nice", Diff: false, Stacked: true},
			{Name: "user", Label: "user", Diff: false, Stacked: true},
		},
	},
	"multicore.loadavg_per_core": {
		Label: "MultiCore loadavg5 per core",
		Unit:  "float",
		Metrics: []mp.Metrics{
			{Name: "loadavg5", Label: "loadavg5", Diff: false, Stacked: false},
		},
	},
}

type saveItem struct {
	LastTime       time.Time
	ProcStatsByCPU map[string]procStats
}

type procStats struct {
	User      *uint64 `json:"user"`
	Nice      *uint64 `json:"nice"`
	System    *uint64 `json:"system"`
	Idle      *uint64 `json:"idle"`
	IoWait    *uint64 `json:"iowait"`
	Irq       *uint64 `json:"irq"`
	SoftIrq   *uint64 `json:"softirq"`
	Steal     *uint64 `json:"steal"`
	Guest     *uint64 `json:"guest"`
	GuestNice *uint64 `json:"guest_nice"`
	Total     uint64  `json:"total"`
}

type cpuPercentages struct {
	CPUName   string
	User      *float64
	Nice      *float64
	System    *float64
	Idle      *float64
	IoWait    *float64
	Irq       *float64
	SoftIrq   *float64
	Steal     *float64
	Guest     *float64
	GuestNice *float64
}

func parseProcStat(out io.Reader) (map[string]procStats, error) {
	scanner := bufio.NewScanner(out)
	var result = make(map[string]procStats)
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.HasPrefix(line, "cpu") {
			continue
		}

		fields := strings.Fields(line)
		key := fields[0]
		values := fields[1:]

		// skip total cpu usage
		if key == "cpu" {
			continue
		}

		var stats procStats
		statPtrs := []**uint64{
			&stats.User,
			&stats.Nice,
			&stats.System,
			&stats.Idle,
			&stats.IoWait,
			&stats.Irq,
			&stats.SoftIrq,
			&stats.Steal,
			&stats.Guest,
			&stats.GuestNice,
		}

		for i, valStr := range values {
			val, err := strconv.ParseUint(valStr, 10, 64)
			if err != nil {
				return nil, err
			}
			*statPtrs[i] = &val
			stats.Total += val
		}

		// Since cpustat[CPUTIME_USER] includes cpustat[CPUTIME_GUEST], subtract the duplicated values from total.
		// https://github.com/torvalds/linux/blob/4ec9f7a18/kernel/sched/cputime.c#L151-L158
		if stats.Guest != nil {
			stats.Total -= *stats.Guest
			*stats.User -= *stats.Guest
		}

		// cpustat[CPUTIME_NICE] includes cpustat[CPUTIME_GUEST_NICE]
		if stats.GuestNice != nil {
			stats.Total -= *stats.GuestNice
			*stats.Nice -= *stats.GuestNice
		}

		result[key] = stats
	}
	return result, nil
}

func collectProcStatValues() (map[string]procStats, error) {
	file, err := os.Open("/proc/stat")
	if err != nil {
		return nil, err
	}
	defer file.Close()
	return parseProcStat(file)
}

func saveValues(tempFileName string, values map[string]procStats, now time.Time) error {
	f, err := os.Create(tempFileName)
	if err != nil {
		return err
	}
	defer f.Close()

	s := saveItem{
		LastTime:       now,
		ProcStatsByCPU: values,
	}

	encoder := json.NewEncoder(f)
	err = encoder.Encode(s)
	if err != nil {
		return err
	}

	return nil
}

func fetchSavedItem(tempFileName string) (*saveItem, error) {
	f, err := os.Open(tempFileName)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer f.Close()

	var stat saveItem
	decoder := json.NewDecoder(f)
	err = decoder.Decode(&stat)
	if err != nil {
		return nil, err
	}
	return &stat, nil
}

func calcCPUUsage(currentValues map[string]procStats, now time.Time, savedItem *saveItem) ([]cpuPercentages, error) {
	if now.Sub(savedItem.LastTime).Seconds() > 600 {
		return nil, errors.New("Too long duration")
	}

	var result []cpuPercentages
	for name, current := range currentValues {
		last, ok := savedItem.ProcStatsByCPU[name]
		if !ok {
			continue
		}
		if last.Total > current.Total {
			return nil, errors.New("cpu counter has been reset")
		}

		user := calculatePercentage(current.User, last.User, current.Total, last.Total)
		nice := calculatePercentage(current.Nice, last.Nice, current.Total, last.Total)
		system := calculatePercentage(current.System, last.System, current.Total, last.Total)
		idle := calculatePercentage(current.Idle, last.Idle, current.Total, last.Total)
		iowait := calculatePercentage(current.IoWait, last.IoWait, current.Total, last.Total)
		irq := calculatePercentage(current.Irq, last.Irq, current.Total, last.Total)
		softirq := calculatePercentage(current.SoftIrq, last.SoftIrq, current.Total, last.Total)
		steal := calculatePercentage(current.Steal, last.Steal, current.Total, last.Total)
		guest := calculatePercentage(current.Guest, last.Guest, current.Total, last.Total)
		// guest_nice available since Linux 2.6.33 (ref: man proc)
		guestNice := calculatePercentage(current.GuestNice, last.GuestNice, current.Total, last.Total)

		result = append(result, cpuPercentages{
			CPUName:   name,
			User:      user,
			Nice:      nice,
			System:    system,
			Idle:      idle,
			IoWait:    iowait,
			Irq:       irq,
			SoftIrq:   softirq,
			Steal:     steal,
			Guest:     guest,
			GuestNice: guestNice,
		})
	}

	return result, nil
}

func calculatePercentage(currentValue *uint64, lastValue *uint64, currentTotal uint64, lastTotal uint64) *float64 {
	if currentValue == nil || lastValue == nil {
		return nil
	}
	ret := float64(*currentValue-*lastValue) / float64(currentTotal-lastTotal) * 100.0
	return &ret
}

func fetchLoadavg5() (float64, error) {
	contentbytes, err := ioutil.ReadFile("/proc/loadavg")
	if err != nil {
		return 0.0, err
	}
	content := string(contentbytes)
	cols := strings.Fields(content)

	if len(cols) > 2 {
		f, err := strconv.ParseFloat(cols[1], 64)
		if err != nil {
			return 0.0, err
		}
		return f, nil
	}
	return 0.0, fmt.Errorf("cannot fetch loadavg5")
}

func printValue(key string, value *float64, time time.Time) {
	if value != nil {
		fmt.Printf("%s\t%f\t%d\n", key, *value, time.Unix())
	}
}

func outputCPUUsage(cpuUsage []cpuPercentages, now time.Time) {
	for _, u := range cpuUsage {
		printValue("multicore.cpu."+u.CPUName+".user", u.User, now)
		printValue("multicore.cpu."+u.CPUName+".nice", u.Nice, now)
		printValue("multicore.cpu."+u.CPUName+".system", u.System, now)
		printValue("multicore.cpu."+u.CPUName+".idle", u.Idle, now)
		printValue("multicore.cpu."+u.CPUName+".iowait", u.IoWait, now)
		printValue("multicore.cpu."+u.CPUName+".irq", u.Irq, now)
		printValue("multicore.cpu."+u.CPUName+".softirq", u.SoftIrq, now)
		printValue("multicore.cpu."+u.CPUName+".steal", u.Steal, now)
		printValue("multicore.cpu."+u.CPUName+".guest", u.Guest, now)
		printValue("multicore.cpu."+u.CPUName+".guest_nice", u.GuestNice, now)
	}
}

func outputLoadavgPerCore(loadavgPerCore float64, now time.Time) {
	printValue("multicore.loadavg_per_core.loadavg5", &loadavgPerCore, now)
}

func outputDefinitions() {
	fmt.Println("# mackerel-agent-plugin")
	var graphs mp.GraphDef
	graphs.Graphs = graphDef

	b, err := json.Marshal(graphs)
	if err != nil {
		log.Fatalln("OutputDefinitions: ", err)
	}
	fmt.Println(string(b))
}

func outputMulticore(tempFileName string) {
	now := time.Now()

	currentValues, err := collectProcStatValues()
	if err != nil {
		log.Fatalln("collectProcStatValues: ", err)
	}

	savedItem, err := fetchSavedItem(tempFileName)
	saveValues(tempFileName, currentValues, now)
	if err != nil {
		log.Fatalln("fetchLastValues: ", err)
	}

	// maybe first time run
	if savedItem == nil {
		return
	}

	cpuUsage, err := calcCPUUsage(currentValues, now, savedItem)
	if err != nil {
		log.Fatalln("calcCPUUsage: ", err)
	}

	loadavg5, err := fetchLoadavg5()
	if err != nil {
		log.Fatalln("fetchLoadavg5: ", err)
	}
	loadPerCPUCount := loadavg5 / (float64(len(cpuUsage)))

	outputCPUUsage(cpuUsage, now)
	outputLoadavgPerCore(loadPerCPUCount, now)
}

func generateTempfilePath() string {
	dir := os.Getenv("MACKEREL_PLUGIN_WORKDIR")
	if dir == "" {
		dir = os.TempDir()
	}
	return filepath.Join(dir, "mackerel-plugin-multicore")
}

// Do the plugin
func Do() {
	var tempFileName string
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	tempFileName = *optTempfile
	if tempFileName == "" {
		tempFileName = generateTempfilePath()
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		outputDefinitions()
	} else {
		outputMulticore(tempFileName)
	}
}
<commit_msg>fast break for proc stat<commit_after>package mpmulticore

import (
	"bufio"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	mp "github.com/mackerelio/go-mackerel-plugin"
)

var graphDef = map[string]mp.Graphs{
	"multicore.cpu.#": {
		Label: "MultiCore CPU",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "guest_nice", Label: "guest_nice", Diff: false, Stacked: true},
			{Name: "guest", Label: "guest", Diff: false, Stacked: true},
			{Name: "steal", Label: "steal", Diff: false, Stacked: true},
			{Name: "softirq", Label: "softirq", Diff: false, Stacked: true},
			{Name: "irq", Label: "irq", Diff: false, Stacked: true},
			{Name: "iowait", Label: "ioWait", Diff: false, Stacked: true},
			{Name: "idle", Label: "idle", Diff: false, Stacked: true},
			{Name: "system", Label: "system", Diff: false, Stacked: true},
			{Name: "nice", Label: "nice", Diff: false, Stacked: true},
			{Name: "user", Label: "user", Diff: false, Stacked: true},
		},
	},
	"multicore.loadavg_per_core": {
		Label: "MultiCore loadavg5 per core",
		Unit:  "float",
		Metrics: []mp.Metrics{
			{Name: "loadavg5", Label: "loadavg5", Diff: false, Stacked: false},
		},
	},
}

type saveItem struct {
	LastTime       time.Time
	ProcStatsByCPU map[string]procStats
}

type procStats struct {
	User      *uint64 `json:"user"`
	Nice      *uint64 `json:"nice"`
	System    *uint64 `json:"system"`
	Idle      *uint64 `json:"idle"`
	IoWait    *uint64 `json:"iowait"`
	Irq       *uint64 `json:"irq"`
	SoftIrq   *uint64 `json:"softirq"`
	Steal     *uint64 `json:"steal"`
	Guest     *uint64 `json:"guest"`
	GuestNice *uint64 `json:"guest_nice"`
	Total     uint64  `json:"total"`
}

type cpuPercentages struct {
	CPUName   string
	User      *float64
	Nice      *float64
	System    *float64
	Idle      *float64
	IoWait    *float64
	Irq       *float64
	SoftIrq   *float64
	Steal     *float64
	Guest     *float64
	GuestNice *float64
}

func parseProcStat(out io.Reader) (map[string]procStats, error) {
	scanner := bufio.NewScanner(out)
	var result = make(map[string]procStats)
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.HasPrefix(line, "cpu") {
			break
		}

		fields := strings.Fields(line)
		key := fields[0]
		values := fields[1:]

		// skip total cpu usage
		if key == "cpu" {
			continue
		}

		var stats procStats
		statPtrs := []**uint64{
			&stats.User,
			&stats.Nice,
			&stats.System,
			&stats.Idle,
			&stats.IoWait,
			&stats.Irq,
			&stats.SoftIrq,
			&stats.Steal,
			&stats.Guest,
			&stats.GuestNice,
		}

		for i, valStr := range values {
			val, err := strconv.ParseUint(valStr, 10, 64)
			if err != nil {
				return nil, err
			}
			*statPtrs[i] = &val
			stats.Total += val
		}

		// Since cpustat[CPUTIME_USER] includes cpustat[CPUTIME_GUEST], subtract the duplicated values from total.
		// https://github.com/torvalds/linux/blob/4ec9f7a18/kernel/sched/cputime.c#L151-L158
		if stats.Guest != nil {
			stats.Total -= *stats.Guest
			*stats.User -= *stats.Guest
		}

		// cpustat[CPUTIME_NICE] includes cpustat[CPUTIME_GUEST_NICE]
		if stats.GuestNice != nil {
			stats.Total -= *stats.GuestNice
			*stats.Nice -= *stats.GuestNice
		}

		result[key] = stats
	}
	return result, nil
}

func collectProcStatValues() (map[string]procStats, error) {
	file, err := os.Open("/proc/stat")
	if err != nil {
		return nil, err
	}
	defer file.Close()
	return parseProcStat(file)
}

func saveValues(tempFileName string, values map[string]procStats, now time.Time) error {
	f, err := os.Create(tempFileName)
	if err != nil {
		return err
	}
	defer f.Close()

	s := saveItem{
		LastTime:       now,
		ProcStatsByCPU: values,
	}

	encoder := json.NewEncoder(f)
	err = encoder.Encode(s)
	if err != nil {
		return err
	}

	return nil
}

func fetchSavedItem(tempFileName string) (*saveItem, error) {
	f, err := os.Open(tempFileName)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer f.Close()

	var stat saveItem
	decoder := json.NewDecoder(f)
	err = decoder.Decode(&stat)
	if err != nil {
		return nil, err
	}
	return &stat, nil
}

func calcCPUUsage(currentValues map[string]procStats, now time.Time, savedItem *saveItem) ([]cpuPercentages, error) {
	if now.Sub(savedItem.LastTime).Seconds() > 600 {
		return nil, errors.New("Too long duration")
	}

	var result []cpuPercentages
	for name, current := range currentValues {
		last, ok := savedItem.ProcStatsByCPU[name]
		if !ok {
			continue
		}
		if last.Total > current.Total {
			return nil, errors.New("cpu counter has been reset")
		}

		user := calculatePercentage(current.User, last.User, current.Total, last.Total)
		nice := calculatePercentage(current.Nice, last.Nice, current.Total, last.Total)
		system := calculatePercentage(current.System, last.System, current.Total, last.Total)
		idle := calculatePercentage(current.Idle, last.Idle, current.Total, last.Total)
		iowait := calculatePercentage(current.IoWait, last.IoWait, current.Total, last.Total)
		irq := calculatePercentage(current.Irq, last.Irq, current.Total, last.Total)
		softirq := calculatePercentage(current.SoftIrq, last.SoftIrq, current.Total, last.Total)
		steal := calculatePercentage(current.Steal, last.Steal, current.Total, last.Total)
		guest := calculatePercentage(current.Guest, last.Guest, current.Total, last.Total)
		// guest_nice available since Linux 2.6.33 (ref: man proc)
		guestNice := calculatePercentage(current.GuestNice, last.GuestNice, current.Total, last.Total)

		result = append(result, cpuPercentages{
			CPUName:   name,
			User:      user,
			Nice:      nice,
			System:    system,
			Idle:      idle,
			IoWait:    iowait,
			Irq:       irq,
			SoftIrq:   softirq,
			Steal:     steal,
			Guest:     guest,
			GuestNice: guestNice,
		})
	}

	return result, nil
}

func calculatePercentage(currentValue *uint64, lastValue *uint64, currentTotal uint64, lastTotal uint64) *float64 {
	if currentValue == nil || lastValue == nil {
		return nil
	}
	ret := float64(*currentValue-*lastValue) / float64(currentTotal-lastTotal) * 100.0
	return &ret
}

func fetchLoadavg5() (float64, error) {
	contentbytes, err := ioutil.ReadFile("/proc/loadavg")
	if err != nil {
		return 0.0, err
	}
	content := string(contentbytes)
	cols := strings.Fields(content)

	if len(cols) > 2 {
		f, err := strconv.ParseFloat(cols[1], 64)
		if err != nil {
			return 0.0, err
		}
		return f, nil
	}
	return 0.0, fmt.Errorf("cannot fetch loadavg5")
}

func printValue(key string, value *float64, time time.Time) {
	if value != nil {
		fmt.Printf("%s\t%f\t%d\n", key, *value, time.Unix())
	}
}

func outputCPUUsage(cpuUsage []cpuPercentages, now time.Time) {
	for _, u := range cpuUsage {
		printValue("multicore.cpu."+u.CPUName+".user", u.User, now)
		printValue("multicore.cpu."+u.CPUName+".nice", u.Nice, now)
		printValue("multicore.cpu."+u.CPUName+".system", u.System, now)
		printValue("multicore.cpu."+u.CPUName+".idle", u.Idle, now)
		printValue("multicore.cpu."+u.CPUName+".iowait", u.IoWait, now)
		printValue("multicore.cpu."+u.CPUName+".irq", u.Irq, now)
		printValue("multicore.cpu."+u.CPUName+".softirq", u.SoftIrq, now)
		printValue("multicore.cpu."+u.CPUName+".steal", u.Steal, now)
		printValue("multicore.cpu."+u.CPUName+".guest", u.Guest, now)
		printValue("multicore.cpu."+u.CPUName+".guest_nice", u.GuestNice, now)
	}
}

func outputLoadavgPerCore(loadavgPerCore float64, now time.Time) {
	printValue("multicore.loadavg_per_core.loadavg5", &loadavgPerCore, now)
}

func outputDefinitions() {
	fmt.Println("# mackerel-agent-plugin")
	var graphs mp.GraphDef
	graphs.Graphs = graphDef

	b, err := json.Marshal(graphs)
	if err != nil {
		log.Fatalln("OutputDefinitions: ", err)
	}
	fmt.Println(string(b))
}

func outputMulticore(tempFileName string) {
	now := time.Now()

	currentValues, err := collectProcStatValues()
	if err != nil {
		log.Fatalln("collectProcStatValues: ", err)
	}

	savedItem, err := fetchSavedItem(tempFileName)
	saveValues(tempFileName, currentValues, now)
	if err != nil {
		log.Fatalln("fetchLastValues: ", err)
	}

	// maybe first time run
	if savedItem == nil {
		return
	}

	cpuUsage, err := calcCPUUsage(currentValues, now, savedItem)
	if err != nil {
		log.Fatalln("calcCPUUsage: ", err)
	}

	loadavg5, err := fetchLoadavg5()
	if err != nil {
		log.Fatalln("fetchLoadavg5: ", err)
	}
	loadPerCPUCount := loadavg5 / (float64(len(cpuUsage)))

	outputCPUUsage(cpuUsage, now)
	outputLoadavgPerCore(loadPerCPUCount, now)
}

func generateTempfilePath() string {
	dir := os.Getenv("MACKEREL_PLUGIN_WORKDIR")
	if dir == "" {
		dir = os.TempDir()
	}
	return filepath.Join(dir, "mackerel-plugin-multicore")
}

// Do the plugin
func Do() {
	var tempFileName string
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	tempFileName = *optTempfile
	if tempFileName == "" {
		tempFileName = generateTempfilePath()
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		outputDefinitions()
	} else {
		outputMulticore(tempFileName)
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"bytes"
	"fmt"
	htmlTemplate "html/template"
	"net/mail"
	"net/url"
	textTemplate "text/template"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"

	"github.com/mozilla-services/reaper/filters"
	"github.com/mozilla-services/reaper/reapable"
	log "github.com/mozilla-services/reaper/reaperlog"
	"github.com/mozilla-services/reaper/state"
)

type CloudformationStack struct {
	AWSResource
	cloudformation.Stack
}

func NewCloudformationStack(region string, stack *cloudformation.Stack) *CloudformationStack {
	a := CloudformationStack{
		AWSResource: AWSResource{
			Region:      reapable.Region(region),
			ID:          reapable.ID(*stack.StackID),
			Name:        *stack.StackName,
			Tags:        make(map[string]string),
			reaperState: state.NewStateWithUntil(time.Now().Add(config.Notifications.FirstStateDuration.Duration)),
		},
		Stack: *stack,
	}

	for i := 0; i < len(stack.Tags); i++ {
		a.AWSResource.Tags[*stack.Tags[i].Key] = *stack.Tags[i].Value
	}

	if a.Tagged(reaperTag) {
		// restore previously tagged state
		a.reaperState = state.NewStateWithTag(a.AWSResource.Tags[reaperTag])
	} else {
		// initial state
		a.reaperState = state.NewStateWithUntilAndState(
			time.Now().Add(config.Notifications.FirstStateDuration.Duration),
			state.FirstState)
	}

	return &a
}

func (a *CloudformationStack) reapableEventHTML(text string) *bytes.Buffer {
	t := htmlTemplate.Must(htmlTemplate.New("reapable").Parse(text))
	buf := bytes.NewBuffer(nil)

	data, err := a.getTemplateData()
	err = t.Execute(buf, data)
	if err != nil {
		log.Debug(fmt.Sprintf("Template generation error: %s", err))
	}
	return buf
}

func (a *CloudformationStack) reapableEventText(text string) *bytes.Buffer {
	t := textTemplate.Must(textTemplate.New("reapable").Parse(text))
	buf := bytes.NewBuffer(nil)

	data, err := a.getTemplateData()
	if err != nil {
		log.Error(fmt.Sprintf("%s", err.Error()))
	}
	err = t.Execute(buf, data)
	if err != nil {
		log.Debug(fmt.Sprintf("Template generation error: %s", err))
	}
	return buf
}

func (a *CloudformationStack) ReapableEventText() *bytes.Buffer {
	return a.reapableEventText(reapableCloudformationEventText)
}

func (a *CloudformationStack) ReapableEventTextShort() *bytes.Buffer {
	return a.reapableEventText(reapableCloudformationEventTextShort)
}

func (a *CloudformationStack) ReapableEventEmail() (owner mail.Address, subject string, body string, err error) {
	// if unowned, return unowned error
	if !a.Owned() {
		err = reapable.UnownedError{fmt.Sprintf("%s does not have an owner tag", a.ReapableDescriptionShort())}
		return
	}

	subject = fmt.Sprintf("AWS Resource %s is going to be Reaped!", a.ReapableDescriptionTiny())
	owner = *a.Owner()
	body = a.reapableEventHTML(reapableCloudformationEventHTML).String()
	return
}

func (a *CloudformationStack) ReapableEventEmailShort() (owner mail.Address, body string, err error) {
	// if unowned, return unowned error
	if !a.Owned() {
		err = reapable.UnownedError{fmt.Sprintf("%s does not have an owner tag", a.ReapableDescriptionShort())}
		return
	}
	owner = *a.Owner()
	body = a.reapableEventHTML(reapableCloudformationEventHTMLShort).String()
	return
}

type CloudformationStackEventData struct {
	Config              *AWSConfig
	CloudformationStack *CloudformationStack
	TerminateLink       string
	StopLink            string
	ForceStopLink       string
	WhitelistLink       string
	IgnoreLink1         string
	IgnoreLink3         string
	IgnoreLink7         string
}

func (a *CloudformationStack) getTemplateData() (*CloudformationStackEventData, error) {
	ignore1, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(1*24*time.Hour))
	ignore3, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(3*24*time.Hour))
	ignore7, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(7*24*time.Hour))
	terminate, err := MakeTerminateLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	stop, err := MakeStopLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	forcestop, err := MakeForceStopLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	whitelist, err := MakeWhitelistLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)

	if err != nil {
		return nil, err
	}

	return &CloudformationStackEventData{
		Config:              config,
		CloudformationStack: a,
		TerminateLink:       terminate,
		StopLink:            stop,
		ForceStopLink:       forcestop,
		WhitelistLink:       whitelist,
		IgnoreLink1:         ignore1,
		IgnoreLink3:         ignore3,
		IgnoreLink7:         ignore7,
	}, nil
}

const reapableCloudformationEventHTML = `

CloudformationStack {{ if .CloudformationStack.Name }}"{{.CloudformationStack.Name}}" {{ end }} in {{.CloudformationStack.Region}} is scheduled to be terminated.

		You can ignore this message and your CloudformationStack will advance to the next state after {{.CloudformationStack.ReaperState.Until}}. If you do not take action it will be terminated!

		You may also choose to:

Terminate it now

Scale it to 0

ForceScale it to 0

Ignore it for 1 more day

Ignore it for 3 more days

Ignore it for 7 more days

		If you want the Reaper to ignore this CloudformationStack tag it with {{ .Config.WhitelistTag }} with any value, or click here.

`

const reapableCloudformationEventHTMLShort = `

CloudformationStack {{ if .CloudformationStack.Name }}"{{.CloudformationStack.Name}}" {{ end }} in {{.CloudformationStack.Region}} is scheduled to be terminated after {{.CloudformationStack.ReaperState.Until}}.

		Terminate,
		Stop,
		Ignore it for 1 more day,
		3 days,
		 7 days, or
		Whitelist it.

`

const reapableCloudformationEventTextShort = `%%%
CloudformationStack [{{.CloudformationStack.ID}}]({{.CloudformationStack.AWSConsoleURL}}) in region: [{{.CloudformationStack.Region}}](https://{{.CloudformationStack.Region}}.console.aws.amazon.com/ec2/v2/home?region={{.CloudformationStack.Region}}).{{if .CloudformationStack.Owned}} Owned by {{.CloudformationStack.Owner}}.\n{{end}}
[Whitelist]({{ .WhitelistLink }}), or [Terminate]({{ .TerminateLink }}) this CloudformationStack.
%%%`

const reapableCloudformationEventText = `%%%
Reaper has discovered an CloudformationStack qualified as reapable: [{{.CloudformationStack.ID}}]({{.CloudformationStack.AWSConsoleURL}}) in region: [{{.CloudformationStack.Region}}](https://{{.CloudformationStack.Region}}.console.aws.amazon.com/ec2/v2/home?region={{.CloudformationStack.Region}}).\n
{{if .CloudformationStack.Owned}}Owned by {{.CloudformationStack.Owner}}.\n{{end}}
{{ if .CloudformationStack.AWSConsoleURL}}{{.CloudformationStack.AWSConsoleURL}}\n{{end}}
[AWS Console URL]({{.CloudformationStack.AWSConsoleURL}})\n
[Whitelist]({{ .WhitelistLink }}) this CloudformationStack.
[Terminate]({{ .TerminateLink }}) this CloudformationStack.
%%%`

// method for reapable -> overrides promoted AWSResource method of same name?
func (a *CloudformationStack) Save(s *state.State) (bool, error) {
	return a.tagReaperState(a.Region, a.ID, a.ReaperState())
}

// method for reapable -> overrides promoted AWSResource method of same name?
func (a *CloudformationStack) Unsave() (bool, error) {
	log.Notice("Unsaving %s", a.ReapableDescriptionTiny())
	return a.untagReaperState(a.Region, a.ID, a.ReaperState())
}

func (a *CloudformationStack) untagReaperState(region reapable.Region, id reapable.ID, newState *state.State) (bool, error) {
	return false, nil
}

func (a *CloudformationStack) tagReaperState(region reapable.Region, id reapable.ID, newState *state.State) (bool, error) {
	return false, nil
}

func (a *CloudformationStack) Filter(filter filters.Filter) bool {
	matched := false
	// map function names to function calls
	switch filter.Function {
	case "Status":
		if *a.StackStatus == filter.Arguments[0] {
			// one of:
			// CREATE_COMPLETE
			// CREATE_IN_PROGRESS
			// CREATE_FAILED
			// DELETE_COMPLETE
			// DELETE_FAILED
			// DELETE_IN_PROGRESS
			// ROLLBACK_COMPLETE
			// ROLLBACK_FAILED
			// ROLLBACK_IN_PROGRESS
			// UPDATE_COMPLETE
			// UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
			// UPDATE_IN_PROGRESS
			// UPDATE_ROLLBACK_COMPLETE
			// UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
			// UPDATE_ROLLBACK_FAILED
			// UPDATE_ROLLBACK_IN_PROGRESS
			matched = true
		}
	case "NotStatus":
		if *a.StackStatus != filter.Arguments[0] {
			matched = true
		}
	case "Tagged":
		if a.Tagged(filter.Arguments[0]) {
			matched = true
		}
	case "NotTagged":
		if !a.Tagged(filter.Arguments[0]) {
			matched = true
		}
	case "TagNotEqual":
		if a.Tag(filter.Arguments[0]) != filter.Arguments[1] {
			matched = true
		}
	case "CreatedTimeInTheLast":
		d, err := time.ParseDuration(filter.Arguments[0])
		if err == nil && time.Since(*a.CreationTime) < d {
			matched = true
		}
	case "CreatedTimeNotInTheLast":
		d, err := time.ParseDuration(filter.Arguments[0])
		if err == nil && time.Since(*a.CreationTime) > d {
			matched = true
		}
	default:
		log.Error(fmt.Sprintf("No function %s could be found for filtering CloudformationStacks.", filter.Function))
	}
	return matched
}

func (a *CloudformationStack) AWSConsoleURL() *url.URL {
	url, err := url.Parse(fmt.Sprintf("https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks?filter=active&tab=overview&stackId=%s",
		string(a.Region), string(a.Region), url.QueryEscape(string(a.ID))))
	if err != nil {
		log.Error(fmt.Sprintf("Error generating AWSConsoleURL. %s", err))
	}
	return url
}

func (a *CloudformationStack) Terminate() (bool, error) {
	log.Notice("Terminating CloudformationStack %s", a.ReapableDescriptionTiny())
	as := cloudformation.New(&aws.Config{Region: string(a.Region)})

	stringID := string(a.ID)

	input := &cloudformation.DeleteStackInput{
		StackName: &stringID,
	}
	_, err := as.DeleteStack(input)
	if err != nil {
		log.Error(fmt.Sprintf("could not delete CloudformationStack %s", a.ReapableDescriptionTiny()))
		return false, err
	}
	return false, nil
}

func (a *CloudformationStack) Stop() (bool, error) {
	return false, nil
}

func (a *CloudformationStack) ForceStop() (bool, error) {
	return false, nil
}
<commit_msg>update notification text for cloudformations<commit_after>package aws

import (
	"bytes"
	"fmt"
	htmlTemplate "html/template"
	"net/mail"
	"net/url"
	textTemplate "text/template"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"

	"github.com/mozilla-services/reaper/filters"
	"github.com/mozilla-services/reaper/reapable"
	log "github.com/mozilla-services/reaper/reaperlog"
	"github.com/mozilla-services/reaper/state"
)

type CloudformationStack struct {
	AWSResource
	cloudformation.Stack
}

func NewCloudformationStack(region string, stack *cloudformation.Stack) *CloudformationStack {
	a := CloudformationStack{
		AWSResource: AWSResource{
			Region:      reapable.Region(region),
			ID:          reapable.ID(*stack.StackID),
			Name:        *stack.StackName,
			Tags:        make(map[string]string),
			reaperState: state.NewStateWithUntil(time.Now().Add(config.Notifications.FirstStateDuration.Duration)),
		},
		Stack: *stack,
	}

	for i := 0; i < len(stack.Tags); i++ {
		a.AWSResource.Tags[*stack.Tags[i].Key] = *stack.Tags[i].Value
	}

	if a.Tagged(reaperTag) {
		// restore previously tagged state
		a.reaperState = state.NewStateWithTag(a.AWSResource.Tags[reaperTag])
	} else {
		// initial state
		a.reaperState = state.NewStateWithUntilAndState(
			time.Now().Add(config.Notifications.FirstStateDuration.Duration),
			state.FirstState)
	}

	return &a
}

func (a *CloudformationStack) reapableEventHTML(text string) *bytes.Buffer {
	t := htmlTemplate.Must(htmlTemplate.New("reapable").Parse(text))
	buf := bytes.NewBuffer(nil)

	data, err := a.getTemplateData()
	err = t.Execute(buf, data)
	if err != nil {
		log.Debug(fmt.Sprintf("Template generation error: %s", err))
	}
	return buf
}

func (a *CloudformationStack) reapableEventText(text string) *bytes.Buffer {
	t := textTemplate.Must(textTemplate.New("reapable").Parse(text))
	buf := bytes.NewBuffer(nil)

	data, err := a.getTemplateData()
	if err != nil {
		log.Error(fmt.Sprintf("%s", err.Error()))
	}
	err = t.Execute(buf, data)
	if err != nil {
		log.Debug(fmt.Sprintf("Template generation error: %s", err))
	}
	return buf
}

func (a *CloudformationStack) ReapableEventText() *bytes.Buffer {
	return a.reapableEventText(reapableCloudformationEventText)
}

func (a *CloudformationStack) ReapableEventTextShort() *bytes.Buffer {
	return a.reapableEventText(reapableCloudformationEventTextShort)
}

func (a *CloudformationStack) ReapableEventEmail() (owner mail.Address, subject string, body string, err error) {
	// if unowned, return unowned error
	if !a.Owned() {
		err = reapable.UnownedError{fmt.Sprintf("%s does not have an owner tag", a.ReapableDescriptionShort())}
		return
	}

	subject = fmt.Sprintf("AWS Resource %s is going to be Reaped!", a.ReapableDescriptionTiny())
	owner = *a.Owner()
	body = a.reapableEventHTML(reapableCloudformationEventHTML).String()
	return
}

func (a *CloudformationStack) ReapableEventEmailShort() (owner mail.Address, body string, err error) {
	// if unowned, return unowned error
	if !a.Owned() {
		err = reapable.UnownedError{fmt.Sprintf("%s does not have an owner tag", a.ReapableDescriptionShort())}
		return
	}
	owner = *a.Owner()
	body = a.reapableEventHTML(reapableCloudformationEventHTMLShort).String()
	return
}

type CloudformationStackEventData struct {
	Config              *AWSConfig
	CloudformationStack *CloudformationStack
	TerminateLink       string
	StopLink            string
	ForceStopLink       string
	WhitelistLink       string
	IgnoreLink1         string
	IgnoreLink3         string
	IgnoreLink7         string
}

func (a *CloudformationStack) getTemplateData() (*CloudformationStackEventData, error) {
	ignore1, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(1*24*time.Hour))
	ignore3, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(3*24*time.Hour))
	ignore7, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(7*24*time.Hour))
	terminate, err := MakeTerminateLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	stop, err := MakeStopLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	forcestop, err := MakeForceStopLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	whitelist, err := MakeWhitelistLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)

	if err != nil {
		return nil, err
	}

	return &CloudformationStackEventData{
		Config:              config,
		CloudformationStack: a,
		TerminateLink:       terminate,
		StopLink:            stop,
		ForceStopLink:       forcestop,
		WhitelistLink:       whitelist,
		IgnoreLink1:         ignore1,
		IgnoreLink3:         ignore3,
		IgnoreLink7:         ignore7,
	}, nil
}

const reapableCloudformationEventHTML = `

Cloudformation {{ if .CloudformationStack.Name }}"{{.CloudformationStack.Name}}" {{ end }} in {{.CloudformationStack.Region}} is scheduled to be terminated.

		You can ignore this message and your Cloudformation will advance to the next state after {{.CloudformationStack.ReaperState.Until}}. If you do not take action it will be terminated!

		You may also choose to:

Terminate it now

Ignore it for 1 more day

Ignore it for 3 more days

Ignore it for 7 more days

		If you want the Reaper to ignore this Cloudformation tag it with {{ .Config.WhitelistTag }} with any value, or click here.

`

const reapableCloudformationEventHTMLShort = `

Cloudformation {{ if .CloudformationStack.Name }}"{{.CloudformationStack.Name}}" {{ end }} in {{.CloudformationStack.Region}} is scheduled to be terminated after {{.CloudformationStack.ReaperState.Until}}.

		Terminate,
		Ignore it for 1 more day,
		3 days,
		 7 days, or
		Whitelist it.

`

const reapableCloudformationEventTextShort = `%%%
Cloudformation [{{.CloudformationStack.ID}}]({{.CloudformationStack.AWSConsoleURL}}) in region: [{{.CloudformationStack.Region}}](https://{{.CloudformationStack.Region}}.console.aws.amazon.com/ec2/v2/home?region={{.CloudformationStack.Region}}).{{if .CloudformationStack.Owned}} Owned by {{.CloudformationStack.Owner}}.\n{{end}}
[Whitelist]({{ .WhitelistLink }}), or [Terminate]({{ .TerminateLink }}) this Cloudformation.
%%%`

const reapableCloudformationEventText = `%%%
Reaper has discovered a Cloudformation qualified as reapable: [{{.CloudformationStack.ID}}]({{.CloudformationStack.AWSConsoleURL}}) in region: [{{.CloudformationStack.Region}}](https://{{.CloudformationStack.Region}}.console.aws.amazon.com/ec2/v2/home?region={{.CloudformationStack.Region}}).\n
{{if .CloudformationStack.Owned}}Owned by {{.CloudformationStack.Owner}}.\n{{end}}
{{ if .CloudformationStack.AWSConsoleURL}}{{.CloudformationStack.AWSConsoleURL}}\n{{end}}
[AWS Console URL]({{.CloudformationStack.AWSConsoleURL}})\n
[Whitelist]({{ .WhitelistLink }}) this Cloudformation.
[Terminate]({{ .TerminateLink }}) this Cloudformation.
%%%`

// method for reapable -> overrides promoted AWSResource method of same name?
func (a *CloudformationStack) Save(s *state.State) (bool, error) {
	return a.tagReaperState(a.Region, a.ID, a.ReaperState())
}

// method for reapable -> overrides promoted AWSResource method of same name?
func (a *CloudformationStack) Unsave() (bool, error) {
	log.Notice("Unsaving %s", a.ReapableDescriptionTiny())
	return a.untagReaperState(a.Region, a.ID, a.ReaperState())
}

func (a *CloudformationStack) untagReaperState(region reapable.Region, id reapable.ID, newState *state.State) (bool, error) {
	return false, nil
}

func (a *CloudformationStack) tagReaperState(region reapable.Region, id reapable.ID, newState *state.State) (bool, error) {
	return false, nil
}

func (a *CloudformationStack) Filter(filter filters.Filter) bool {
	matched := false
	// map function names to function calls
	switch filter.Function {
	case "Status":
		if *a.StackStatus == filter.Arguments[0] {
			// one of:
			// CREATE_COMPLETE
			// CREATE_IN_PROGRESS
			// CREATE_FAILED
			// DELETE_COMPLETE
			// DELETE_FAILED
			// DELETE_IN_PROGRESS
			// ROLLBACK_COMPLETE
			// ROLLBACK_FAILED
			// ROLLBACK_IN_PROGRESS
			// UPDATE_COMPLETE
			// UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
			// UPDATE_IN_PROGRESS
			// UPDATE_ROLLBACK_COMPLETE
			// UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
			// UPDATE_ROLLBACK_FAILED
			// UPDATE_ROLLBACK_IN_PROGRESS
			matched = true
		}
	case "NotStatus":
		if *a.StackStatus != filter.Arguments[0] {
			matched = true
		}
	case "Tagged":
		if a.Tagged(filter.Arguments[0]) {
			matched = true
		}
	case "NotTagged":
		if !a.Tagged(filter.Arguments[0]) {
			matched = true
		}
	case "TagNotEqual":
		if a.Tag(filter.Arguments[0]) != filter.Arguments[1] {
			matched = true
		}
	case "CreatedTimeInTheLast":
		d, err := time.ParseDuration(filter.Arguments[0])
		if err == nil && time.Since(*a.CreationTime) < d {
			matched = true
		}
	case "CreatedTimeNotInTheLast":
		d, err := time.ParseDuration(filter.Arguments[0])
		if err == nil && time.Since(*a.CreationTime) > d {
			matched = true
		}
	default:
		log.Error(fmt.Sprintf("No function %s could be found for filtering CloudformationStacks.", filter.Function))
	}
	return matched
}

func (a *CloudformationStack) AWSConsoleURL() *url.URL {
	url, err := url.Parse(fmt.Sprintf("https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks?filter=active&tab=overview&stackId=%s",
		string(a.Region), string(a.Region), url.QueryEscape(string(a.ID))))
	if err != nil {
		log.Error(fmt.Sprintf("Error generating AWSConsoleURL. %s", err))
	}
	return url
}

func (a *CloudformationStack) Terminate() (bool, error) {
	log.Notice("Terminating CloudformationStack %s", a.ReapableDescriptionTiny())
	as := cloudformation.New(&aws.Config{Region: string(a.Region)})

	stringID := string(a.ID)

	input := &cloudformation.DeleteStackInput{
		StackName: &stringID,
	}
	_, err := as.DeleteStack(input)
	if err != nil {
		log.Error(fmt.Sprintf("could not delete CloudformationStack %s", a.ReapableDescriptionTiny()))
		return false, err
	}
	return false, nil
}

func (a *CloudformationStack) Stop() (bool, error) {
	return false, nil
}

func (a *CloudformationStack) ForceStop() (bool, error) {
	return false, nil
}
<|endoftext|>


<commit_before>package main

// SessionState objects represent a current API session, mainly used for rate limiting.
type SessionState struct {
	ExpiresIn     int64                       `json:"expires_in"`
	OauthClientID string                      `json:"oauth_client_id"`
	AccessToken   string                      `json:"client_id"`
	TokenType     string                      `json:"token_type"`
}
<commit_msg>Fixed session state ison definition<commit_after>package main

// Enums for keys to be stored in a session context - this is how gorilla expects
// these to be implemented and is lifted pretty much from docs
const (
	SessionData = "session_data"
	AuthHeaderValue = "auth_header"
)

// SessionState objects represent a current API session, mainly used for rate limiting.
type SessionState struct {
	ExpiresIn     int64                       `json:"expires_in"`
	OauthClientID string                      `json:"oauth_client_id"`
	AccessToken   string                      `json:"access_token"`
	TokenType     string                      `json:"token_type"`
}
<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2014 Boldly Go Ventures

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

// Package predicate defines the Predicate interface and provides basic predicates.
package predicate

// X represents a value to be evaluated by a Predicate.
type X interface{}

type Predicate interface {
	P(X) bool
}

// PredicateFunc maps X to bool
type PredicateFunc func(X) bool

// P satisfies the Predicate interface.
func (p PredicateFunc) P(x X) bool {
	return p(x)
}

// True returns a PredicateFunc that will always return true, for any x.
func True() Predicate {
	return PredicateFunc(func(x X) bool {
		return true
	})
}

// True returns a PredicateFunc that will always return false, for any x.
func False() Predicate {
	return PredicateFunc(func(x X) bool {
		return false
	})
}

type Set []Predicate

type And Set

// P returns true if and only all of its member Predicates is true for x. The logic is short circuited,
// returning false when a member Predicate is false.
func (p And) P(x X) bool {
	for _, p := range p {
		if !p.P(x) {
			return false
		}
	}

	return true
}

type Or Set

// P returns true if any of its member Predicates is true for x. The logic is short circuited,
// returning true when a member Predicate is true.
func (p Or) P(x X) bool {
	for _, p := range p {
		if p.P(x) {
			return true
		}
	}

	return false
}

type Xor Set

// P returns true if and only if one of its member Predicates is true for x. The logic is short circuited, returning
// false when a second member Predicate is true.
func (p Xor) P(x X) bool {
	var n int
	for _, p := range p {
		if p.P(x) {
			n++
		}

		// short circuit
		if n > 1 {
			return false
		}
	}

	return n == 1
}

// Not returns a PredicateFunc that will return false if any of the passed Predicates is true for x.
func Not(p ...Predicate) Predicate {
	return PredicateFunc(func(x X) bool {
		return !Or(p).P(x)
	})
}

func Exists(k string, s interface{}) Predicate {
	return PredicateFunc(func(x X) bool {
		return true
	})
}
<commit_msg>Added type specific logic to Exists()<commit_after>/*
The MIT License (MIT)

Copyright (c) 2014 Boldly Go Ventures

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

// Package predicate defines the Predicate interface and provides basic predicates.
package predicate

// X represents a value to be evaluated by a Predicate.
type X interface{}

type Predicate interface {
	P(X) bool
}

// PredicateFunc maps X to bool
type PredicateFunc func(X) bool

// P satisfies the Predicate interface.
func (p PredicateFunc) P(x X) bool {
	return p(x)
}

// True returns a PredicateFunc that will always return true, for any x.
func True() Predicate {
	return PredicateFunc(func(x X) bool {
		return true
	})
}

// True returns a PredicateFunc that will always return false, for any x.
func False() Predicate {
	return PredicateFunc(func(x X) bool {
		return false
	})
}

type Set []Predicate

type And Set

// P returns true if and only all of its member Predicates is true for x. The logic is short circuited,
// returning false when a member Predicate is false.
func (p And) P(x X) bool {
	for _, p := range p {
		if !p.P(x) {
			return false
		}
	}

	return true
}

type Or Set

// P returns true if any of its member Predicates is true for x. The logic is short circuited,
// returning true when a member Predicate is true.
func (p Or) P(x X) bool {
	for _, p := range p {
		if p.P(x) {
			return true
		}
	}

	return false
}

type Xor Set

// P returns true if and only if one of its member Predicates is true for x. The logic is short circuited, returning
// false when a second member Predicate is true.
func (p Xor) P(x X) bool {
	var n int
	for _, p := range p {
		if p.P(x) {
			n++
		}

		// short circuit
		if n > 1 {
			return false
		}
	}

	return n == 1
}

// Not returns a PredicateFunc that will return false if any of the passed Predicates is true for x.
func Not(p ...Predicate) Predicate {
	return PredicateFunc(func(x X) bool {
		return !Or(p).P(x)
	})
}

// Exists returns a PredicateFunc that will return true only if x[k] is in set s.
func Exists(k string, s interface{}) Predicate {
	return PredicateFunc(func(x X) bool {
		if y, ok := x.(map[string]interface{}); ok {
			x = y[k]
		}

		switch s := s.(type) {
		case map[string]interface{}:
			for _, v := range s {
				if v == x {
					return true
				}
			}
		case []interface{}:
			for _, v := range s {
				if v == x {
					return true
				}
			}
		case string, float64, bool, nil:
			return s == x
		}

		return false
	})
}
<|endoftext|>