<commit_before>package captain // import "github.com/harbur/captain"

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"runtime"
)

// Debug can be turned on to enable debug mode.
var Debug bool

// StatusError provides error code and id
type StatusError struct {
	error  error
	status int
}

// Pre function executes commands on pre section before build
func Pre(config Config, app App) {
	for _, value := range app.Pre {
		info("Running pre command: %s", value)
		res := execute("bash", "-c", value)
		if res != nil {
			err("Pre execution returned non-zero status")
			os.Exit(TestFailed)
		}
	}
}

// Post function executes commands on pre section after build
func Post(config Config, app App) {
	for _, value := range app.Post {
		info("Running post command: %s", value)
		res := execute("bash", "-c", value)
		if res != nil {
			err("Post execution returned non-zero status")
			os.Exit(TestFailed)
		}
	}
}

type BuildOptions struct {
	Config       Config
	Force        bool
	All_branches bool
	Long_sha     bool
	Branch_tags  bool
	Commit_tags  bool
}

// Build function compiles the Containers of the project
func Build(opts BuildOptions) {
	config := opts.Config

	var rev = getRevision(opts.Long_sha)

	// For each App
	for _, app := range config.GetApps() {
		// If no Git repo exist
		if !isGit() {
			// Perfoming [build latest]
			debug("No local git repository found, just building latest")

			// Execute Pre commands
			Pre(config, app)

			// Build latest image
			res := buildImage(app, "latest", opts.Force)
			if res != nil {
				os.Exit(BuildFailed)
			}
		} else {
			// Skip build if there are no local changes and the commit is already built
			if !isDirty() && imageExist(app, rev) && !opts.Force {
				// Performing [skip rev|tag rev@latest|tag rev@branch]
				info("Skipping build of %s:%s - image is already built", app.Image, rev)

				// Tag commit image
				tagImage(app, rev, "latest")

				// Tag branch image
				for _, branch := range getBranches(opts.All_branches) {
					res := tagImage(app, rev, branch)
					if res != nil {
						os.Exit(TagFailed)
					}
				}

			} else {
				// Performing [build latest|tag latest@rev|tag latest@branch]

				// Execute Pre commands
				Pre(config, app)

				// Build latest image
				res := buildImage(app, "latest", opts.Force)
				if res != nil {
					os.Exit(BuildFailed)
				}
				if isDirty() {
					debug("Skipping tag of %s:%s - local changes exist", app.Image, rev)
				} else {
					// Tag commit image
					tagImage(app, "latest", rev)

					// Tag branch image
					for _, branch := range getBranches(opts.All_branches) {
						res := tagImage(app, "latest", branch)
						if res != nil {
							os.Exit(TagFailed)
						}
					}
				}
			}
		}
		Post(config, app)
	}
}

// Test function executes the tests of the project
func Test(opts BuildOptions) {
	config := opts.Config

	for _, app := range config.GetApps() {
		for _, value := range app.Test {
			info("Running test command: %s", value)
			res := execute("bash", "-c", value)
			if res != nil {
				err("Test execution returned non-zero status")
				os.Exit(TestFailed)
			}
		}
	}
}

// Push function pushes the containers to the remote registry
func Push(opts BuildOptions) {
	config := opts.Config

	// If no Git repo exist
	if !isGit() {
		err("No local git repository found, cannot push")
		os.Exit(NoGit)
	}

	if isDirty() {
		err("Git repository has local changes, cannot push")
		os.Exit(GitDirty)
	}

	for _, app := range config.GetApps() {
		for _, branch := range getBranches(opts.All_branches) {
			info("Pushing image %s:%s", app.Image, "latest")
			execute("docker", "push", app.Image+":"+"latest")
			if opts.Branch_tags {
				info("Pushing image %s:%s", app.Image, branch)
				execute("docker", "push", app.Image+":"+branch)
			}
			if opts.Commit_tags {
				rev := getRevision(opts.Long_sha)
				info("Pushing image %s:%s", app.Image, rev)
				execute("docker", "push", app.Image+":"+rev)
			}
		}
	}
}

// Pull function pulls the containers from the remote registry
func Pull(opts BuildOptions) {
	config := opts.Config

	for _, app := range config.GetApps() {
		for _, branch := range getBranches(opts.All_branches) {
			info("Pulling image %s:%s", app.Image, "latest")
			execute("docker", "pull", app.Image+":"+"latest")
			if opts.Branch_tags {
				info("Pulling image %s:%s", app.Image, branch)
				execute("docker", "pull", app.Image+":"+branch)
			}
			if opts.Commit_tags {
				rev := getRevision(opts.Long_sha)
				info("Pulling image %s:%s", app.Image, rev)
				execute("docker", "pull", app.Image+":"+rev)
			}
		}
	}
}

// Purge function purges the stale images
func Purge(opts BuildOptions) {
	config := opts.Config

	// For each App
	for _, app := range config.GetApps() {
		var tags = []string{}

		// Retrieve the list of the existing Image tags
		for _, img := range getImages(app) {
			tags = append(tags, img.RepoTags...)
		}

		// Remove from the list: The latest image
		for key, tag := range tags {
			if tag == app.Image+":latest" {
				tags = append(tags[:key], tags[key+1:]...)
			}
		}

		// Remove from the list: The current commit-id
		for key, tag := range tags {
			if tag == app.Image+":"+getRevision(opts.Long_sha) {
				tags = append(tags[:key], tags[key+1:]...)
			}
		}

		// Remove from the list: The working-dir git branches
		for _, branch := range getBranches(opts.All_branches) {
			for key, tag := range tags {
				if tag == app.Image+":"+branch {
					tags = append(tags[:key], tags[key+1:]...)
				}
			}
		}

		// Proceed with deletion of Images
		for _, tag := range tags {
			info("Deleting image %s", tag)
			res := removeImage(tag)
			if res != nil {
				err("Deleting image failed: %s", res)
				os.Exit(DeleteImageFailed)
			}
		}
	}
}

func SelfUpdate() {
	captainDir := os.Getenv("HOME") + "/.captain"
	binariesDir := captainDir + "/binaries"
	binDir := captainDir + "/bin"
	version := findLastVersion()
	kernel := runtime.GOOS
	arch := runtime.GOARCH
	// downloadUrl := fmt.Sprintf("https://github.com/harbur/captain/releases/download/%s/captain-%s-%s", version, kernel, arch)
	downloadUrl := fmt.Sprintf("https://github.com/harbur/captain/releases/download/%s/captain-%s-%s", version, "Darwin", "x86_64")
	versionPath := binariesDir + "/" + version

	info("Self update called!")
	info("captainDir %s", captainDir)
	info("binariesDir %s", binariesDir)
	info("binDir %s", binDir)
	info("version %s", version)
	info("kernel %s", kernel)
	info("arch %s", arch)
	info("downloadUrl %s", downloadUrl)

	err := downloadFile(versionPath, downloadUrl)
	if err != nil {
		fmt.Println(err)
	}

	err = os.Chmod(versionPath, 0755)
	if err != nil {
		fmt.Println(err)
	}
}

func findLastVersion() string {
	return "v0.8.0"
}

func downloadFile(filepath string, url string) error {

	// Create the file
	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()

	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Writer the body to file
	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>working self-update implementation<commit_after>package captain // import "github.com/harbur/captain"

import (
	"fmt"
	"gopkg.in/cheggaaa/pb.v1"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
)

// Debug can be turned on to enable debug mode.
var Debug bool

// StatusError provides error code and id
type StatusError struct {
	error  error
	status int
}

// Pre function executes commands on pre section before build
func Pre(config Config, app App) {
	for _, value := range app.Pre {
		info("Running pre command: %s", value)
		res := execute("bash", "-c", value)
		if res != nil {
			err("Pre execution returned non-zero status")
			os.Exit(TestFailed)
		}
	}
}

// Post function executes commands on pre section after build
func Post(config Config, app App) {
	for _, value := range app.Post {
		info("Running post command: %s", value)
		res := execute("bash", "-c", value)
		if res != nil {
			err("Post execution returned non-zero status")
			os.Exit(TestFailed)
		}
	}
}

type BuildOptions struct {
	Config       Config
	Force        bool
	All_branches bool
	Long_sha     bool
	Branch_tags  bool
	Commit_tags  bool
}

// Build function compiles the Containers of the project
func Build(opts BuildOptions) {
	config := opts.Config

	var rev = getRevision(opts.Long_sha)

	// For each App
	for _, app := range config.GetApps() {
		// If no Git repo exist
		if !isGit() {
			// Perfoming [build latest]
			debug("No local git repository found, just building latest")

			// Execute Pre commands
			Pre(config, app)

			// Build latest image
			res := buildImage(app, "latest", opts.Force)
			if res != nil {
				os.Exit(BuildFailed)
			}
		} else {
			// Skip build if there are no local changes and the commit is already built
			if !isDirty() && imageExist(app, rev) && !opts.Force {
				// Performing [skip rev|tag rev@latest|tag rev@branch]
				info("Skipping build of %s:%s - image is already built", app.Image, rev)

				// Tag commit image
				tagImage(app, rev, "latest")

				// Tag branch image
				for _, branch := range getBranches(opts.All_branches) {
					res := tagImage(app, rev, branch)
					if res != nil {
						os.Exit(TagFailed)
					}
				}

			} else {
				// Performing [build latest|tag latest@rev|tag latest@branch]

				// Execute Pre commands
				Pre(config, app)

				// Build latest image
				res := buildImage(app, "latest", opts.Force)
				if res != nil {
					os.Exit(BuildFailed)
				}
				if isDirty() {
					debug("Skipping tag of %s:%s - local changes exist", app.Image, rev)
				} else {
					// Tag commit image
					tagImage(app, "latest", rev)

					// Tag branch image
					for _, branch := range getBranches(opts.All_branches) {
						res := tagImage(app, "latest", branch)
						if res != nil {
							os.Exit(TagFailed)
						}
					}
				}
			}
		}
		Post(config, app)
	}
}

// Test function executes the tests of the project
func Test(opts BuildOptions) {
	config := opts.Config

	for _, app := range config.GetApps() {
		for _, value := range app.Test {
			info("Running test command: %s", value)
			res := execute("bash", "-c", value)
			if res != nil {
				err("Test execution returned non-zero status")
				os.Exit(TestFailed)
			}
		}
	}
}

// Push function pushes the containers to the remote registry
func Push(opts BuildOptions) {
	config := opts.Config

	// If no Git repo exist
	if !isGit() {
		err("No local git repository found, cannot push")
		os.Exit(NoGit)
	}

	if isDirty() {
		err("Git repository has local changes, cannot push")
		os.Exit(GitDirty)
	}

	for _, app := range config.GetApps() {
		for _, branch := range getBranches(opts.All_branches) {
			info("Pushing image %s:%s", app.Image, "latest")
			execute("docker", "push", app.Image+":"+"latest")
			if opts.Branch_tags {
				info("Pushing image %s:%s", app.Image, branch)
				execute("docker", "push", app.Image+":"+branch)
			}
			if opts.Commit_tags {
				rev := getRevision(opts.Long_sha)
				info("Pushing image %s:%s", app.Image, rev)
				execute("docker", "push", app.Image+":"+rev)
			}
		}
	}
}

// Pull function pulls the containers from the remote registry
func Pull(opts BuildOptions) {
	config := opts.Config

	for _, app := range config.GetApps() {
		for _, branch := range getBranches(opts.All_branches) {
			info("Pulling image %s:%s", app.Image, "latest")
			execute("docker", "pull", app.Image+":"+"latest")
			if opts.Branch_tags {
				info("Pulling image %s:%s", app.Image, branch)
				execute("docker", "pull", app.Image+":"+branch)
			}
			if opts.Commit_tags {
				rev := getRevision(opts.Long_sha)
				info("Pulling image %s:%s", app.Image, rev)
				execute("docker", "pull", app.Image+":"+rev)
			}
		}
	}
}

// Purge function purges the stale images
func Purge(opts BuildOptions) {
	config := opts.Config

	// For each App
	for _, app := range config.GetApps() {
		var tags = []string{}

		// Retrieve the list of the existing Image tags
		for _, img := range getImages(app) {
			tags = append(tags, img.RepoTags...)
		}

		// Remove from the list: The latest image
		for key, tag := range tags {
			if tag == app.Image+":latest" {
				tags = append(tags[:key], tags[key+1:]...)
			}
		}

		// Remove from the list: The current commit-id
		for key, tag := range tags {
			if tag == app.Image+":"+getRevision(opts.Long_sha) {
				tags = append(tags[:key], tags[key+1:]...)
			}
		}

		// Remove from the list: The working-dir git branches
		for _, branch := range getBranches(opts.All_branches) {
			for key, tag := range tags {
				if tag == app.Image+":"+branch {
					tags = append(tags[:key], tags[key+1:]...)
				}
			}
		}

		// Proceed with deletion of Images
		for _, tag := range tags {
			info("Deleting image %s", tag)
			res := removeImage(tag)
			if res != nil {
				err("Deleting image failed: %s", res)
				os.Exit(DeleteImageFailed)
			}
		}
	}
}

func SelfUpdate() {
	captainDir := filepath.FromSlash(os.Getenv("HOME") + "/.captain")
	binariesDir := filepath.FromSlash(captainDir + "/binaries")
	binDir := filepath.FromSlash(captainDir + "/bin")

	kernel := runtime.GOOS
	arch := runtime.GOARCH
	captainSymlinkPath := filepath.FromSlash(binDir + "/captain")
	currentVersionPath, err := os.Readlink(captainSymlinkPath)

	info("Checking the last version of Captain...")
	version := findLastVersion()
	downloadUrl := fmt.Sprintf("https://github.com/harbur/captain/releases/download/%s/captain-%s-%s", version, kernel, arch)
	downloadUrl = fmt.Sprintf("https://github.com/harbur/captain/releases/download/%s/captain-%s-%s", version, "Darwin", "x86_64")
	downloadedVersionPath := filepath.FromSlash(binariesDir + "/captain-" + version)

	if currentVersionPath == downloadedVersionPath {
		info("You have installed the last version of captain (%s)", version)
		return
	}

	info("New version available, start downloading %s", version)

	// create binaries dir
	if err = os.MkdirAll(binariesDir, 0755); err != nil {
		fmt.Println(err)
	}

	// download new version
	if err = downloadFile(downloadedVersionPath, downloadUrl); err != nil {
		fmt.Println(err)
	}

	// grant excution to download version
	if err = os.Chmod(downloadedVersionPath, 0755); err != nil {
		fmt.Println(err)
	}

	info("Downloaded captain %s", version)

	if err = os.MkdirAll(binDir, 0755); err != nil {
		fmt.Println(err)
	}

	if _, err := os.Stat(captainSymlinkPath); err == nil {
		os.Remove(captainSymlinkPath)
	}

	if err = os.Symlink(downloadedVersionPath, captainSymlinkPath); err != nil {
		fmt.Println(err)
	}

	info("Installed captain %s", version)
}

func findLastVersion() string {
	return "v0.8.0"
}

func downloadFile(filepath string, url string) error {

	// Create the file
	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()

	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// create and start bar
	bar := pb.New64(resp.ContentLength).SetUnits(pb.U_BYTES).Start()
	defer bar.Finish()

	// create proxy reader
	reader := bar.NewProxyReader(resp.Body)

	// and copy from pb reader
	_, err = io.Copy(out, reader)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package captain // import "github.com/harbur/captain"

import (
	"fmt"
	"gopkg.in/cheggaaa/pb.v1"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
)

// Debug can be turned on to enable debug mode.
var Debug bool

// StatusError provides error code and id
type StatusError struct {
	error  error
	status int
}

// Pre function executes commands on pre section before build
func Pre(config Config, app App) {
	for _, value := range app.Pre {
		info("Running pre command: %s", value)
		res := execute("bash", "-c", value)
		if res != nil {
			err("Pre execution returned non-zero status")
			os.Exit(TestFailed)
		}
	}
}

// Post function executes commands on pre section after build
func Post(config Config, app App) {
	for _, value := range app.Post {
		info("Running post command: %s", value)
		res := execute("bash", "-c", value)
		if res != nil {
			err("Post execution returned non-zero status")
			os.Exit(TestFailed)
		}
	}
}

type BuildOptions struct {
	Config       Config
	Force        bool
	All_branches bool
	Long_sha     bool
	Branch_tags  bool
	Commit_tags  bool
}

// Build function compiles the Containers of the project
func Build(opts BuildOptions) {
	config := opts.Config

	var rev = getRevision(opts.Long_sha)

	// For each App
	for _, app := range config.GetApps() {
		// If no Git repo exist
		if !isGit() {
			// Perfoming [build latest]
			debug("No local git repository found, just building latest")

			// Execute Pre commands
			Pre(config, app)

			// Build latest image
			res := buildImage(app, "latest", opts.Force)
			if res != nil {
				os.Exit(BuildFailed)
			}
		} else {
			// Skip build if there are no local changes and the commit is already built
			if !isDirty() && imageExist(app, rev) && !opts.Force {
				// Performing [skip rev|tag rev@latest|tag rev@branch]
				info("Skipping build of %s:%s - image is already built", app.Image, rev)

				// Tag commit image
				tagImage(app, rev, "latest")

				// Tag branch image
				for _, branch := range getBranches(opts.All_branches) {
					res := tagImage(app, rev, branch)
					if res != nil {
						os.Exit(TagFailed)
					}
				}

			} else {
				// Performing [build latest|tag latest@rev|tag latest@branch]

				// Execute Pre commands
				Pre(config, app)

				// Build latest image
				res := buildImage(app, "latest", opts.Force)
				if res != nil {
					os.Exit(BuildFailed)
				}
				if isDirty() {
					debug("Skipping tag of %s:%s - local changes exist", app.Image, rev)
				} else {
					// Tag commit image
					tagImage(app, "latest", rev)

					// Tag branch image
					for _, branch := range getBranches(opts.All_branches) {
						res := tagImage(app, "latest", branch)
						if res != nil {
							os.Exit(TagFailed)
						}
					}
				}
			}
		}
		Post(config, app)
	}
}

// Test function executes the tests of the project
func Test(opts BuildOptions) {
	config := opts.Config

	for _, app := range config.GetApps() {
		for _, value := range app.Test {
			info("Running test command: %s", value)
			res := execute("bash", "-c", value)
			if res != nil {
				err("Test execution returned non-zero status")
				os.Exit(TestFailed)
			}
		}
	}
}

// Push function pushes the containers to the remote registry
func Push(opts BuildOptions) {
	config := opts.Config

	// If no Git repo exist
	if !isGit() {
		err("No local git repository found, cannot push")
		os.Exit(NoGit)
	}

	if isDirty() {
		err("Git repository has local changes, cannot push")
		os.Exit(GitDirty)
	}

	for _, app := range config.GetApps() {
		for _, branch := range getBranches(opts.All_branches) {
			info("Pushing image %s:%s", app.Image, "latest")
			execute("docker", "push", app.Image+":"+"latest")
			if opts.Branch_tags {
				info("Pushing image %s:%s", app.Image, branch)
				execute("docker", "push", app.Image+":"+branch)
			}
			if opts.Commit_tags {
				rev := getRevision(opts.Long_sha)
				info("Pushing image %s:%s", app.Image, rev)
				execute("docker", "push", app.Image+":"+rev)
			}
		}
	}
}

// Pull function pulls the containers from the remote registry
func Pull(opts BuildOptions) {
	config := opts.Config

	for _, app := range config.GetApps() {
		for _, branch := range getBranches(opts.All_branches) {
			info("Pulling image %s:%s", app.Image, "latest")
			execute("docker", "pull", app.Image+":"+"latest")
			if opts.Branch_tags {
				info("Pulling image %s:%s", app.Image, branch)
				execute("docker", "pull", app.Image+":"+branch)
			}
			if opts.Commit_tags {
				rev := getRevision(opts.Long_sha)
				info("Pulling image %s:%s", app.Image, rev)
				execute("docker", "pull", app.Image+":"+rev)
			}
		}
	}
}

// Purge function purges the stale images
func Purge(opts BuildOptions) {
	config := opts.Config

	// For each App
	for _, app := range config.GetApps() {
		var tags = []string{}

		// Retrieve the list of the existing Image tags
		for _, img := range getImages(app) {
			tags = append(tags, img.RepoTags...)
		}

		// Remove from the list: The latest image
		for key, tag := range tags {
			if tag == app.Image+":latest" {
				tags = append(tags[:key], tags[key+1:]...)
			}
		}

		// Remove from the list: The current commit-id
		for key, tag := range tags {
			if tag == app.Image+":"+getRevision(opts.Long_sha) {
				tags = append(tags[:key], tags[key+1:]...)
			}
		}

		// Remove from the list: The working-dir git branches
		for _, branch := range getBranches(opts.All_branches) {
			for key, tag := range tags {
				if tag == app.Image+":"+branch {
					tags = append(tags[:key], tags[key+1:]...)
				}
			}
		}

		// Proceed with deletion of Images
		for _, tag := range tags {
			info("Deleting image %s", tag)
			res := removeImage(tag)
			if res != nil {
				err("Deleting image failed: %s", res)
				os.Exit(DeleteImageFailed)
			}
		}
	}
}

func SelfUpdate() {
	captainDir := filepath.FromSlash(os.Getenv("HOME") + "/.captain")
	binariesDir := filepath.FromSlash(captainDir + "/binaries")
	binDir := filepath.FromSlash(captainDir + "/bin")

	kernel := runtime.GOOS
	arch := runtime.GOARCH
	captainSymlinkPath := filepath.FromSlash(binDir + "/captain")
	currentVersionPath, err := os.Readlink(captainSymlinkPath)

	info("Checking the last version of Captain...")
	version := findLastVersion()
	downloadUrl := fmt.Sprintf("https://github.com/harbur/captain/releases/download/%s/captain-%s-%s", version, kernel, arch)
	downloadUrl = fmt.Sprintf("https://github.com/harbur/captain/releases/download/%s/captain-%s-%s", version, "Darwin", "x86_64")
	downloadedVersionPath := filepath.FromSlash(binariesDir + "/captain-" + version)

	if currentVersionPath == downloadedVersionPath {
		info("You have installed the last version of captain (%s)", version)
		return
	}

	info("New version available, start downloading %s", version)

	// create binaries dir
	if err = os.MkdirAll(binariesDir, 0755); err != nil {
		fmt.Println(err)
	}

	// download new version
	if err = downloadFile(downloadedVersionPath, downloadUrl); err != nil {
		fmt.Println(err)
	}

	// grant excution to download version
	if err = os.Chmod(downloadedVersionPath, 0755); err != nil {
		fmt.Println(err)
	}

	info("Downloaded captain %s", version)

	if err = os.MkdirAll(binDir, 0755); err != nil {
		fmt.Println(err)
	}

	if _, err := os.Stat(captainSymlinkPath); err == nil {
		os.Remove(captainSymlinkPath)
	}

	if err = os.Symlink(downloadedVersionPath, captainSymlinkPath); err != nil {
		fmt.Println(err)
	}

	info("Installed captain %s", version)
}

func findLastVersion() string {
	return "v0.8.0"
}

func downloadFile(filepath string, url string) error {

	// Create the file
	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()

	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// create and start bar
	bar := pb.New64(resp.ContentLength).SetUnits(pb.U_BYTES).Start()
	defer bar.Finish()

	// create proxy reader
	reader := bar.NewProxyReader(resp.Body)

	// and copy from pb reader
	_, err = io.Copy(out, reader)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Updated self-update to retrieve version from github<commit_after>package captain // import "github.com/harbur/captain"

import (
	"fmt"
	"gopkg.in/cheggaaa/pb.v1"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
)

// Debug can be turned on to enable debug mode.
var Debug bool

// StatusError provides error code and id
type StatusError struct {
	error  error
	status int
}

// Pre function executes commands on pre section before build
func Pre(config Config, app App) {
	for _, value := range app.Pre {
		info("Running pre command: %s", value)
		res := execute("bash", "-c", value)
		if res != nil {
			err("Pre execution returned non-zero status")
			os.Exit(TestFailed)
		}
	}
}

// Post function executes commands on pre section after build
func Post(config Config, app App) {
	for _, value := range app.Post {
		info("Running post command: %s", value)
		res := execute("bash", "-c", value)
		if res != nil {
			err("Post execution returned non-zero status")
			os.Exit(TestFailed)
		}
	}
}

type BuildOptions struct {
	Config       Config
	Force        bool
	All_branches bool
	Long_sha     bool
	Branch_tags  bool
	Commit_tags  bool
}

// Build function compiles the Containers of the project
func Build(opts BuildOptions) {
	config := opts.Config

	var rev = getRevision(opts.Long_sha)

	// For each App
	for _, app := range config.GetApps() {
		// If no Git repo exist
		if !isGit() {
			// Perfoming [build latest]
			debug("No local git repository found, just building latest")

			// Execute Pre commands
			Pre(config, app)

			// Build latest image
			res := buildImage(app, "latest", opts.Force)
			if res != nil {
				os.Exit(BuildFailed)
			}
		} else {
			// Skip build if there are no local changes and the commit is already built
			if !isDirty() && imageExist(app, rev) && !opts.Force {
				// Performing [skip rev|tag rev@latest|tag rev@branch]
				info("Skipping build of %s:%s - image is already built", app.Image, rev)

				// Tag commit image
				tagImage(app, rev, "latest")

				// Tag branch image
				for _, branch := range getBranches(opts.All_branches) {
					res := tagImage(app, rev, branch)
					if res != nil {
						os.Exit(TagFailed)
					}
				}

			} else {
				// Performing [build latest|tag latest@rev|tag latest@branch]

				// Execute Pre commands
				Pre(config, app)

				// Build latest image
				res := buildImage(app, "latest", opts.Force)
				if res != nil {
					os.Exit(BuildFailed)
				}
				if isDirty() {
					debug("Skipping tag of %s:%s - local changes exist", app.Image, rev)
				} else {
					// Tag commit image
					tagImage(app, "latest", rev)

					// Tag branch image
					for _, branch := range getBranches(opts.All_branches) {
						res := tagImage(app, "latest", branch)
						if res != nil {
							os.Exit(TagFailed)
						}
					}
				}
			}
		}
		Post(config, app)
	}
}

// Test function executes the tests of the project
func Test(opts BuildOptions) {
	config := opts.Config

	for _, app := range config.GetApps() {
		for _, value := range app.Test {
			info("Running test command: %s", value)
			res := execute("bash", "-c", value)
			if res != nil {
				err("Test execution returned non-zero status")
				os.Exit(TestFailed)
			}
		}
	}
}

// Push function pushes the containers to the remote registry
func Push(opts BuildOptions) {
	config := opts.Config

	// If no Git repo exist
	if !isGit() {
		err("No local git repository found, cannot push")
		os.Exit(NoGit)
	}

	if isDirty() {
		err("Git repository has local changes, cannot push")
		os.Exit(GitDirty)
	}

	for _, app := range config.GetApps() {
		for _, branch := range getBranches(opts.All_branches) {
			info("Pushing image %s:%s", app.Image, "latest")
			execute("docker", "push", app.Image+":"+"latest")
			if opts.Branch_tags {
				info("Pushing image %s:%s", app.Image, branch)
				execute("docker", "push", app.Image+":"+branch)
			}
			if opts.Commit_tags {
				rev := getRevision(opts.Long_sha)
				info("Pushing image %s:%s", app.Image, rev)
				execute("docker", "push", app.Image+":"+rev)
			}
		}
	}
}

// Pull function pulls the containers from the remote registry
func Pull(opts BuildOptions) {
	config := opts.Config

	for _, app := range config.GetApps() {
		for _, branch := range getBranches(opts.All_branches) {
			info("Pulling image %s:%s", app.Image, "latest")
			execute("docker", "pull", app.Image+":"+"latest")
			if opts.Branch_tags {
				info("Pulling image %s:%s", app.Image, branch)
				execute("docker", "pull", app.Image+":"+branch)
			}
			if opts.Commit_tags {
				rev := getRevision(opts.Long_sha)
				info("Pulling image %s:%s", app.Image, rev)
				execute("docker", "pull", app.Image+":"+rev)
			}
		}
	}
}

// Purge function purges the stale images
func Purge(opts BuildOptions) {
	config := opts.Config

	// For each App
	for _, app := range config.GetApps() {
		var tags = []string{}

		// Retrieve the list of the existing Image tags
		for _, img := range getImages(app) {
			tags = append(tags, img.RepoTags...)
		}

		// Remove from the list: The latest image
		for key, tag := range tags {
			if tag == app.Image+":latest" {
				tags = append(tags[:key], tags[key+1:]...)
			}
		}

		// Remove from the list: The current commit-id
		for key, tag := range tags {
			if tag == app.Image+":"+getRevision(opts.Long_sha) {
				tags = append(tags[:key], tags[key+1:]...)
			}
		}

		// Remove from the list: The working-dir git branches
		for _, branch := range getBranches(opts.All_branches) {
			for key, tag := range tags {
				if tag == app.Image+":"+branch {
					tags = append(tags[:key], tags[key+1:]...)
				}
			}
		}

		// Proceed with deletion of Images
		for _, tag := range tags {
			info("Deleting image %s", tag)
			res := removeImage(tag)
			if res != nil {
				err("Deleting image failed: %s", res)
				os.Exit(DeleteImageFailed)
			}
		}
	}
}

func SelfUpdate() {
	captainDir := filepath.FromSlash(os.Getenv("HOME") + "/.captain")
	binariesDir := filepath.FromSlash(captainDir + "/binaries")
	binDir := filepath.FromSlash(captainDir + "/bin")

	kernel := runtime.GOOS
	arch := runtime.GOARCH
	captainSymlinkPath := filepath.FromSlash(binDir + "/captain")
	currentVersionPath, _ := os.Readlink(captainSymlinkPath)

	info("Checking the last version of Captain...")
	version := findLastVersion()
	downloadUrl := fmt.Sprintf("https://github.com/harbur/captain/releases/download/%s/captain-%s-%s", version, kernel, arch)
	downloadedVersionPath := filepath.FromSlash(binariesDir + "/captain-" + version)

	if currentVersionPath == downloadedVersionPath {
		info("You have installed the last version of captain (%s)", version)
		return
	}

	info("New version available, start downloading %s", version)

	// create binaries dir
	if err := os.MkdirAll(binariesDir, 0755); err != nil {
		fmt.Println(err)
	}

	// download new version
	if err := downloadFile(downloadedVersionPath, downloadUrl); err != nil {
		fmt.Println(err)
	}

	// grant excution to download version
	if err := os.Chmod(downloadedVersionPath, 0755); err != nil {
		fmt.Println(err)
	}

	info("Downloaded captain %s", version)

	if err := os.MkdirAll(binDir, 0755); err != nil {
		fmt.Println(err)
	}

	if _, err := os.Stat(captainSymlinkPath); err == nil {
		os.Remove(captainSymlinkPath)
	}

	if err := os.Symlink(downloadedVersionPath, captainSymlinkPath); err != nil {
		fmt.Println(err)
	}

	info("Installed captain %s", version)
}

func findLastVersion() string {
	url := "https://raw.githubusercontent.com/harbur/captain/master/VERSION"

	res, err := http.Get(url)

	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	content, err := ioutil.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	return string(content)
}

func downloadFile(filepath string, url string) error {

	// Create the file
	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()

	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// create and start bar
	bar := pb.New64(resp.ContentLength).SetUnits(pb.U_BYTES).Start()
	defer bar.Finish()

	// create proxy reader
	reader := bar.NewProxyReader(resp.Body)

	// and copy from pb reader
	_, err = io.Copy(out, reader)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Wandkiva, LLC

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.andg/licenses/LICENSE-2.0

Unless required by applicable law and 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 and implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package bitarray

func andSparseWithSparseBitArray(sba *sparseBitArray,
	other *sparseBitArray) BitArray {

	max := maxInt64(int64(len(sba.indices)), int64(len(other.indices)))
	indices := make(uintSlice, 0, max)
	blocks := make(blocks, 0, max)

	selfIndex := 0
	otherIndex := 0
	for {
		// last comparison was a real and, we are both exhausted now
		if selfIndex == len(sba.indices) && otherIndex == len(other.indices) {
			break
		} else if selfIndex == len(sba.indices) {
			break
		} else if otherIndex == len(other.indices) {
			break
		}

		selfValue := sba.indices[selfIndex]
		otherValue := other.indices[otherIndex]

		switch diff := int(otherValue) - int(selfValue); {
		case diff > 0:
			indices = append(indices, selfValue)
			blocks = append(blocks, sba.blocks[selfIndex])
			selfIndex++
		case diff < 0:
			indices = append(indices, otherValue)
			blocks = append(blocks, other.blocks[otherIndex])
			otherIndex++
		default:
			indices = append(indices, otherValue)
			blocks = append(blocks, sba.blocks[selfIndex].and(other.blocks[otherIndex]))
			selfIndex++
			otherIndex++
		}
	}

	return &sparseBitArray{
		indices: indices,
		blocks:  blocks,
	}
}

func andSparseWithDenseBitArray(sba *sparseBitArray, other *bitArray) BitArray {
	max := maxUint64(uint64(sba.Capacity()), uint64(other.Capacity()))

	ba := newBitArray(max * s)
	selfIndex := 0
	otherIndex := 0
	for {
		if selfIndex == len(sba.indices) && otherIndex == len(other.blocks) {
			break
		} else if selfIndex == len(sba.indices) {
			break
		} else if otherIndex == len(other.blocks) {
			for i, value := range sba.indices[selfIndex:] {
				ba.blocks[value] = sba.blocks[i+selfIndex]
			}
			break
		}

		selfValue := sba.indices[selfIndex]
		if selfValue == uint64(otherIndex) {
			ba.blocks[otherIndex] = sba.blocks[selfIndex].and(other.blocks[otherIndex])
			selfIndex++
			otherIndex++
			continue
		}

		ba.blocks[otherIndex] = other.blocks[otherIndex]
		otherIndex++
	}

	ba.setHighest()
	ba.setLowest()

	return ba
}

func andDenseWithDenseBitArray(dba *bitArray, other *bitArray) BitArray {
	max := maxUint64(uint64(len(dba.blocks)), uint64(len(other.blocks)))

	ba := newBitArray(max * s)

	for i := uint64(0); i < max; i++ {
		if i == uint64(len(dba.blocks)) {
			break
		}

		if i == uint64(len(other.blocks)) {
			break
		}

		ba.blocks[i] = dba.blocks[i].and(other.blocks[i])
	}

	ba.setLowest()
	ba.setHighest()

	return ba
}
<commit_msg>Simplify AND code<commit_after>/*
Copyright 2014 Wandkiva, LLC

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.andg/licenses/LICENSE-2.0

Unless required by applicable law and 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 and implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package bitarray

func andSparseWithSparseBitArray(sba *sparseBitArray,
	other *sparseBitArray) BitArray {

	max := maxInt64(int64(len(sba.indices)), int64(len(other.indices)))
	indices := make(uintSlice, 0, max)
	blocks := make(blocks, 0, max)

	selfIndex := 0
	otherIndex := 0
	for {
		// last comparison was a real and, we are both exhausted now
		if selfIndex == len(sba.indices) || otherIndex == len(other.indices) {
			break
		}

		selfValue := sba.indices[selfIndex]
		otherValue := other.indices[otherIndex]

		switch diff := otherValue - selfValue; {
		case diff > 0:
			selfIndex++
		case diff < 0:
			otherIndex++
		default:
			indices = append(indices, otherValue)
			blocks = append(blocks, sba.blocks[selfIndex].and(other.blocks[otherIndex]))
			selfIndex++
			otherIndex++
		}
	}

	return &sparseBitArray{
		indices: indices,
		blocks:  blocks,
	}
}

func andSparseWithDenseBitArray(sba *sparseBitArray, other *bitArray) BitArray {
	max := maxUint64(uint64(sba.Capacity()), uint64(other.Capacity()))

	ba := newBitArray(max * s)
	selfIndex := 0
	otherIndex := 0
	for {
		if selfIndex == len(sba.indices) || otherIndex == len(other.blocks) {
			break
		}

		selfValue := sba.indices[selfIndex]
		if selfValue == uint64(otherIndex) {
			ba.blocks[otherIndex] = sba.blocks[selfIndex].and(other.blocks[otherIndex])
			selfIndex++
			otherIndex++
			continue
		}

		ba.blocks[otherIndex] = other.blocks[otherIndex]
		otherIndex++
	}

	ba.setHighest()
	ba.setLowest()

	return ba
}

func andDenseWithDenseBitArray(dba *bitArray, other *bitArray) BitArray {
	max := maxUint64(uint64(len(dba.blocks)), uint64(len(other.blocks)))

	ba := newBitArray(max * s)

	for i := uint64(0); i < max; i++ {
		if i == uint64(len(dba.blocks)) || i == uint64(len(dba.blocks)) {
			break
		}

		ba.blocks[i] = dba.blocks[i].and(other.blocks[i])
	}

	ba.setLowest()
	ba.setHighest()

	return ba
}
<|endoftext|>


<commit_before>package libbroadcast

var globalBroadcastManager *BroadcastManager

type BroadcastManager struct {
	broadcasters map[string]*Broadcaster
}

func init() {
	globalBroadcastManager = &BroadcastManager{
		broadcasters: make(map[string]*Broadcaster),
	}
}

func Global() *BroadcastManager {
	return globalBroadcastManager
}

func (broadcastManager *BroadcastManager) CreateBroadcaster(name string) *Broadcaster {
	// don't allow duplicated names to be created (since that will be a bad situation)
	if b, ok := broadcastManager.broadcasters[name]; ok {
		return b
	}

	broadcaster := newBroadcaster(name)
	broadcastManager.broadcasters[name] = broadcaster
	return broadcaster
}

func (broadcastManager *BroadcastManager) GetBroadcaster(name string) *Broadcaster {
	return broadcastManager.CreateBroadcaster(name)
}

func (broadcastManager *BroadcastManager) Destroy(name string) {
	delete(broadcastManager.broadcasters, name)
}
<commit_msg>[REPLICATED-1545] libbroadcast is not threadsafe, causes crashes.<commit_after>package libbroadcast

import (
	"sync"
)

var (
	globalBroadcastManager *BroadcastManager
)

type BroadcastManager struct {
	broadcasters map[string]*Broadcaster
	mutex        *sync.Mutex
}

func init() {
	globalBroadcastManager = &BroadcastManager{
		broadcasters: make(map[string]*Broadcaster),
		mutex:        &sync.Mutex{},
	}
}

func Global() *BroadcastManager {
	return globalBroadcastManager
}

func (broadcastManager *BroadcastManager) CreateBroadcaster(name string) *Broadcaster {
	broadcastManager.mutex.Lock()
	defer broadcastManager.mutex.Unlock()

	// don't allow duplicated names to be created (since that will be a bad situation)
	if b, ok := broadcastManager.broadcasters[name]; ok {
		return b
	}

	broadcaster := newBroadcaster(name)
	broadcastManager.broadcasters[name] = broadcaster
	return broadcaster
}

func (broadcastManager *BroadcastManager) GetBroadcaster(name string) *Broadcaster {
	return broadcastManager.CreateBroadcaster(name)
}

func (broadcastManager *BroadcastManager) Destroy(name string) {
	broadcastManager.mutex.Lock()
	defer broadcastManager.mutex.Unlock()

	delete(broadcastManager.broadcasters, name)
}
<|endoftext|>


<commit_before>package css

import (
	"fmt"
	"github.com/gorilla/css/scanner"
	"strings"
)

/*
	stylesheet  : [ CDO | CDC | S | statement ]*;
	statement   : ruleset | at-rule;
	at-rule     : ATKEYWORD S* any* [ block | ';' S* ];
	block       : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*;
	ruleset     : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*;
	selector    : any+;
	declaration : property S* ':' S* value;
	property    : IDENT;
	value       : [ any | block | ATKEYWORD S* ]+;
	any         : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
	              | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
	              | DASHMATCH | ':' | FUNCTION S* [any|unused]* ')'
	              | '(' S* [any|unused]* ')' | '[' S* [any|unused]* ']'
	              ] S*;
	unused      : block | ATKEYWORD S* | ';' S* | CDO S* | CDC S*;
*/

type blockParserContext struct {
	State        State
	NowProperty  string
	NowValue     string
	NowImportant int
}

// ParseBlock take a string of a css block,
// parses it and returns a map of css style declarations.
func ParseBlock(csstext string) map[string]*CSSStyleDeclaration {
	s := scanner.New(csstext)
	return parseBlock(s)
}

func parseBlock(s *scanner.Scanner) map[string]*CSSStyleDeclaration {
	/* block       : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*;
	property    : IDENT;
	value       : [ any | block | ATKEYWORD S* ]+;
	any         : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
	              | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
	              | DASHMATCH | ':' | FUNCTION S* [any|unused]* ')'
	              | '(' S* [any|unused]* ')' | '[' S* [any|unused]* ']'
	              ] S*;
	*/
	decls := make(map[string]*CSSStyleDeclaration)

	context := &blockParserContext{
		State:        STATE_NONE,
		NowProperty:  "",
		NowValue:     "",
		NowImportant: 0,
	}

	for {
		token := s.Next()

		//fmt.Printf("BLOCK(%d): %s:'%s'\n", context.State, token.Type.String(), token.Value)

		if token.Type == scanner.TokenError {
			break
		}

		if token.Type == scanner.TokenEOF {
			if context.State == STATE_VALUE {
				// we are ending without ; or }
				// this can happen when we parse only css declaration
				decl := NewCSSStyleDeclaration(context.NowProperty, strings.TrimSpace(context.NowValue), context.NowImportant)
				decls[context.NowProperty] = decl
			}
			break
		}

		switch token.Type {

		case scanner.TokenS:
			if context.State == STATE_VALUE {
				context.NowValue += token.Value
			}
		case scanner.TokenIdent:
			if context.State == STATE_NONE {
				context.State = STATE_PROPERTY
				context.NowProperty = strings.TrimSpace(token.Value)
				break
			}
			if token.Value == "important" {
				context.NowImportant = 1
			} else {
				context.NowValue += token.Value
			}
		case scanner.TokenChar:
			if context.State == STATE_NONE {
				if token.Value == "{" {
					break
				}
			}
			if context.State == STATE_PROPERTY {
				if token.Value == ":" {
					context.State = STATE_VALUE
				}
				// CHAR and STATE_PROPERTY but not : then weird
				// break to ignore it
				break
			}
			// should be no state or value
			if token.Value == ";" {
				decl := NewCSSStyleDeclaration(context.NowProperty, strings.TrimSpace(context.NowValue), context.NowImportant)
				decls[context.NowProperty] = decl
				context.NowProperty = ""
				context.NowValue = ""
				context.NowImportant = 0
				context.State = STATE_NONE
			} else if token.Value == "}" { // last property in a block can have optional ;
				if context.State == STATE_VALUE {
					// only valid if state is still VALUE, could be ;}
					decl := NewCSSStyleDeclaration(context.NowProperty, strings.TrimSpace(context.NowValue), context.NowImportant)
					decls[context.NowProperty] = decl
				}
				// we are done
				return decls
			} else if token.Value != "!" {
				context.NowValue += token.Value
			}
			break

		// any
		case scanner.TokenNumber:
			fallthrough
		case scanner.TokenPercentage:
			fallthrough
		case scanner.TokenDimension:
			fallthrough
		case scanner.TokenString:
			fallthrough
		case scanner.TokenURI:
			fallthrough
		case scanner.TokenHash:
			fallthrough
		case scanner.TokenUnicodeRange:
			fallthrough
		case scanner.TokenIncludes:
			fallthrough
		case scanner.TokenDashMatch:
			fallthrough
		case scanner.TokenFunction:
			fallthrough
		case scanner.TokenSubstringMatch:
			context.NowValue += token.Value
		}
	}

	return decls
}
<commit_msg>Comment out import of fmt<commit_after>package css

import (
	//"fmt"
	"github.com/gorilla/css/scanner"
	"strings"
)

/*
	stylesheet  : [ CDO | CDC | S | statement ]*;
	statement   : ruleset | at-rule;
	at-rule     : ATKEYWORD S* any* [ block | ';' S* ];
	block       : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*;
	ruleset     : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*;
	selector    : any+;
	declaration : property S* ':' S* value;
	property    : IDENT;
	value       : [ any | block | ATKEYWORD S* ]+;
	any         : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
	              | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
	              | DASHMATCH | ':' | FUNCTION S* [any|unused]* ')'
	              | '(' S* [any|unused]* ')' | '[' S* [any|unused]* ']'
	              ] S*;
	unused      : block | ATKEYWORD S* | ';' S* | CDO S* | CDC S*;
*/

type blockParserContext struct {
	State        State
	NowProperty  string
	NowValue     string
	NowImportant int
}

// ParseBlock take a string of a css block,
// parses it and returns a map of css style declarations.
func ParseBlock(csstext string) map[string]*CSSStyleDeclaration {
	s := scanner.New(csstext)
	return parseBlock(s)
}

func parseBlock(s *scanner.Scanner) map[string]*CSSStyleDeclaration {
	/* block       : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*;
	property    : IDENT;
	value       : [ any | block | ATKEYWORD S* ]+;
	any         : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
	              | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
	              | DASHMATCH | ':' | FUNCTION S* [any|unused]* ')'
	              | '(' S* [any|unused]* ')' | '[' S* [any|unused]* ']'
	              ] S*;
	*/
	decls := make(map[string]*CSSStyleDeclaration)

	context := &blockParserContext{
		State:        STATE_NONE,
		NowProperty:  "",
		NowValue:     "",
		NowImportant: 0,
	}

	for {
		token := s.Next()

		//fmt.Printf("BLOCK(%d): %s:'%s'\n", context.State, token.Type.String(), token.Value)

		if token.Type == scanner.TokenError {
			break
		}

		if token.Type == scanner.TokenEOF {
			if context.State == STATE_VALUE {
				// we are ending without ; or }
				// this can happen when we parse only css declaration
				decl := NewCSSStyleDeclaration(context.NowProperty, strings.TrimSpace(context.NowValue), context.NowImportant)
				decls[context.NowProperty] = decl
			}
			break
		}

		switch token.Type {

		case scanner.TokenS:
			if context.State == STATE_VALUE {
				context.NowValue += token.Value
			}
		case scanner.TokenIdent:
			if context.State == STATE_NONE {
				context.State = STATE_PROPERTY
				context.NowProperty = strings.TrimSpace(token.Value)
				break
			}
			if token.Value == "important" {
				context.NowImportant = 1
			} else {
				context.NowValue += token.Value
			}
		case scanner.TokenChar:
			if context.State == STATE_NONE {
				if token.Value == "{" {
					break
				}
			}
			if context.State == STATE_PROPERTY {
				if token.Value == ":" {
					context.State = STATE_VALUE
				}
				// CHAR and STATE_PROPERTY but not : then weird
				// break to ignore it
				break
			}
			// should be no state or value
			if token.Value == ";" {
				decl := NewCSSStyleDeclaration(context.NowProperty, strings.TrimSpace(context.NowValue), context.NowImportant)
				decls[context.NowProperty] = decl
				context.NowProperty = ""
				context.NowValue = ""
				context.NowImportant = 0
				context.State = STATE_NONE
			} else if token.Value == "}" { // last property in a block can have optional ;
				if context.State == STATE_VALUE {
					// only valid if state is still VALUE, could be ;}
					decl := NewCSSStyleDeclaration(context.NowProperty, strings.TrimSpace(context.NowValue), context.NowImportant)
					decls[context.NowProperty] = decl
				}
				// we are done
				return decls
			} else if token.Value != "!" {
				context.NowValue += token.Value
			}
			break

		// any
		case scanner.TokenNumber:
			fallthrough
		case scanner.TokenPercentage:
			fallthrough
		case scanner.TokenDimension:
			fallthrough
		case scanner.TokenString:
			fallthrough
		case scanner.TokenURI:
			fallthrough
		case scanner.TokenHash:
			fallthrough
		case scanner.TokenUnicodeRange:
			fallthrough
		case scanner.TokenIncludes:
			fallthrough
		case scanner.TokenDashMatch:
			fallthrough
		case scanner.TokenFunction:
			fallthrough
		case scanner.TokenSubstringMatch:
			context.NowValue += token.Value
		}
	}

	return decls
}
<|endoftext|>


<commit_before>// Copyright 2015
// Author: huangjunwei@youmi.net

package blog4go

import (
	"testing"
)

func BenchmarkFormat(b *testing.B) {
	b.StopTimer()
	writer, _ := NewFileLogWriter("output.log")

	b.StartTimer()

	for i := 0; i < b.N; i++ {
		writer.Debugf("haha %s. en\\en, always %d and %f", "eddie", 18, 3.1415)
	}
}
<commit_msg>统一格式化<commit_after>// Copyright 2015
// Author: huangjunwei@youmi.net

package blog4go

import (
	"testing"
)

func BenchmarkFormat(b *testing.B) {
	b.StopTimer()
	writer, _ := NewFileLogWriter("output.log")

	b.StartTimer()

	for i := 0; i < b.N; i++ {
		writer.Debugf("haha %s. en\\en, always %d and %.4f", "eddie", 18, 3.1415)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"github.com/smancke/guble/protocol"

	"fmt"
	"net/http"
	"reflect"
	"time"
	"github.com/docker/distribution/health"
)

// Startable interface for modules which provide a start mechanism
type Startable interface {
	Start() error
}

// Stopable interface for modules which provide a stop mechanism
type Stopable interface {
	Stop() error
}

// Endpoint adds a HTTP handler for the `GetPrefix()` to the webserver
type Endpoint interface {
	http.Handler
	GetPrefix() string
}

// Service is the main class for simple control of a server
type Service struct {
	webServer  *WebServer
	router     Router
	stopables  []Stopable
	startables []Startable
	// The time given to each Module on Stop()
	StopGracePeriod time.Duration
}

// NewService registers the Main Router, where other modules can subscribe for messages
func NewService(
	addr string,
	router Router) *Service {
	service := &Service{
		stopables:       make([]Stopable, 0, 5),
		webServer:       NewWebServer(addr),
		router:          router,
		StopGracePeriod: time.Second * 2,
	}
	service.Register(service.webServer)
	service.Register(service.router)

	service.webServer.Handle("/health", http.HandlerFunc(health.StatusHandler))

	return service
}

// Register the supplied module on this service.
// This method checks the module for the following interfaces and
// does the expected registrations:
//   Startable,
//   Stopable: notify when the service stops
//   health.Checker:
//   Endpoint: Register the handler function of the Endpoint in the http service at prefix
func (service *Service) Register(module interface{}) {
	name := reflect.TypeOf(module).String()

	if startable, ok := module.(Startable); ok {
		protocol.Info("register %v as StartListener", name)
		service.AddStartable(startable)
	}

	if stopable, ok := module.(Stopable); ok {
		protocol.Info("register %v as StopListener", name)
		service.AddStopable(stopable)
	}

	if checker, ok := module.(health.Checker); ok {
		protocol.Info("register %v as HealthChecker", name)
		//TODO parameterize / configure frequency and threshold
		health.RegisterPeriodicThresholdFunc(name, time.Second*60, 1, health.CheckFunc(checker.Check))
	}

	if endpoint, ok := module.(Endpoint); ok {
		protocol.Info("register %v as Endpoint to %v", name, endpoint.GetPrefix())
		service.webServer.Handle(endpoint.GetPrefix(), endpoint)
	}
}

func (service *Service) Start() error {
	el := protocol.NewErrorList("Errors occured while startup the service: ")

	for _, startable := range service.startables {
		name := reflect.TypeOf(startable).String()

		protocol.Debug("starting module %v", name)
		if err := startable.Start(); err != nil {
			protocol.Err("error on startup module %v", name)
			el.Add(err)
		}
	}
	return el.ErrorOrNil()
}

func (service *Service) Stop() error {
	errors := make(map[string]error)
	for _, stopable := range service.stopables {
		name := reflect.TypeOf(stopable).String()
		stoppedChan := make(chan bool)
		errorChan := make(chan error)
		protocol.Info("stopping %v ...", name)
		go func() {
			err := stopable.Stop()
			if err != nil {
				errorChan <- err
				return
			}
			stoppedChan <- true
		}()
		select {
		case err := <-errorChan:
			protocol.Err("error while stopping %v: %v", name, err.Error)
			errors[name] = err
		case <-stoppedChan:
			protocol.Info("stopped %v", name)
		case <-time.After(service.StopGracePeriod):
			errors[name] = fmt.Errorf("error while stopping %v: not returned after %v seconds", name, service.StopGracePeriod)
			protocol.Err(errors[name].Error())
		}
	}
	if len(errors) > 0 {
		return fmt.Errorf("Errors while stopping modules %q", errors)
	}
	return nil
}

func (service *Service) AddStopable(stopable Stopable) {
	service.stopables = append(service.stopables, stopable)
}

func (service *Service) AddStartable(startable Startable) {
	service.startables = append(service.startables, startable)
}

func (service *Service) GetWebServer() *WebServer {
	return service.webServer
}
<commit_msg>add general properties in service for health: frequency, threshold<commit_after>package server

import (
	"github.com/smancke/guble/protocol"

	"fmt"
	"net/http"
	"reflect"
	"time"
	"github.com/docker/distribution/health"
)

// Startable interface for modules which provide a start mechanism
type Startable interface {
	Start() error
}

// Stopable interface for modules which provide a stop mechanism
type Stopable interface {
	Stop() error
}

// Endpoint adds a HTTP handler for the `GetPrefix()` to the webserver
type Endpoint interface {
	http.Handler
	GetPrefix() string
}

// Service is the main class for simple control of a server
type Service struct {
	webServer  *WebServer
	router     Router
	stopables  []Stopable
	startables []Startable
	// The time given to each Module on Stop()
	StopGracePeriod time.Duration
	healthCheckFrequency time.Duration
	healthCheckThreshold int
}

// NewService registers the Main Router, where other modules can subscribe for messages
func NewService(
	addr string,
	router Router) *Service {
	service := &Service{
		stopables:       make([]Stopable, 0, 5),
		webServer:       NewWebServer(addr),
		router:          router,
		StopGracePeriod: time.Second * 2,
	}
	service.Register(service.webServer)
	service.Register(service.router)

	service.webServer.Handle("/health", http.HandlerFunc(health.StatusHandler))

	return service
}

// Register the supplied module on this service.
// This method checks the module for the following interfaces and
// does the expected registrations:
//   Startable,
//   Stopable: notify when the service stops
//   health.Checker:
//   Endpoint: Register the handler function of the Endpoint in the http service at prefix
func (service *Service) Register(module interface{}) {
	name := reflect.TypeOf(module).String()

	if startable, ok := module.(Startable); ok {
		protocol.Info("register %v as StartListener", name)
		service.AddStartable(startable)
	}

	if stopable, ok := module.(Stopable); ok {
		protocol.Info("register %v as StopListener", name)
		service.AddStopable(stopable)
	}

	if checker, ok := module.(health.Checker); ok {
		protocol.Info("register %v as HealthChecker", name)
		//TODO parameterize / configure frequency and threshold
		health.RegisterPeriodicThresholdFunc(name, service.healthCheckFrequency, service.healthCheckThreshold, health.CheckFunc(checker.Check))
	}

	if endpoint, ok := module.(Endpoint); ok {
		protocol.Info("register %v as Endpoint to %v", name, endpoint.GetPrefix())
		service.webServer.Handle(endpoint.GetPrefix(), endpoint)
	}
}

func (service *Service) Start() error {
	el := protocol.NewErrorList("Errors occured while startup the service: ")

	for _, startable := range service.startables {
		name := reflect.TypeOf(startable).String()

		protocol.Debug("starting module %v", name)
		if err := startable.Start(); err != nil {
			protocol.Err("error on startup module %v", name)
			el.Add(err)
		}
	}
	return el.ErrorOrNil()
}

func (service *Service) Stop() error {
	errors := make(map[string]error)
	for _, stopable := range service.stopables {
		name := reflect.TypeOf(stopable).String()
		stoppedChan := make(chan bool)
		errorChan := make(chan error)
		protocol.Info("stopping %v ...", name)
		go func() {
			err := stopable.Stop()
			if err != nil {
				errorChan <- err
				return
			}
			stoppedChan <- true
		}()
		select {
		case err := <-errorChan:
			protocol.Err("error while stopping %v: %v", name, err.Error)
			errors[name] = err
		case <-stoppedChan:
			protocol.Info("stopped %v", name)
		case <-time.After(service.StopGracePeriod):
			errors[name] = fmt.Errorf("error while stopping %v: not returned after %v seconds", name, service.StopGracePeriod)
			protocol.Err(errors[name].Error())
		}
	}
	if len(errors) > 0 {
		return fmt.Errorf("Errors while stopping modules %q", errors)
	}
	return nil
}

func (service *Service) AddStopable(stopable Stopable) {
	service.stopables = append(service.stopables, stopable)
}

func (service *Service) AddStartable(startable Startable) {
	service.startables = append(service.startables, startable)
}

func (service *Service) GetWebServer() *WebServer {
	return service.webServer
}
<|endoftext|>


<commit_before>package server

import (
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/webserver"

	"github.com/docker/distribution/health"

	"fmt"
	"github.com/smancke/guble/metrics"
	"net/http"
	"reflect"
	"time"
)

const (
	defaultHealthFrequency = time.Second * 60
	defaultHealthThreshold = 1
)

// Startable interface for modules which provide a start mechanism
type Startable interface {
	Start() error
}

// Stopable interface for modules which provide a stop mechanism
type Stopable interface {
	Stop() error
}

// Endpoint adds a HTTP handler for the `GetPrefix()` to the webserver
type Endpoint interface {
	http.Handler
	GetPrefix() string
}

// Service is the main class for simple control of a server
type Service struct {
	webserver       *webserver.WebServer
	router          Router
	modules         []interface{}
	healthEndpoint  string
	healthFrequency time.Duration
	healthThreshold int
	metricsEndpoint string
}

// NewService registers the Main Router, where other modules can subscribe for messages
func NewService(router Router, webserver *webserver.WebServer) *Service {
	service := &Service{
		webserver:       webserver,
		router:          router,
		healthFrequency: defaultHealthFrequency,
		healthThreshold: defaultHealthThreshold,
	}
	service.RegisterModules(service.router, service.webserver)
	return service
}

func (s *Service) RegisterModules(modules ...interface{}) {
	protocol.Debug("service: RegisterModules: adding %d modules after existing %d modules", len(modules), len(s.modules))
	s.modules = append(s.modules, modules...)
}

func (s *Service) HealthEndpointPrefix(value string) *Service {
	s.healthEndpoint = value
	return s
}

func (s *Service) HealthFrequency(value time.Duration) *Service {
	s.healthFrequency = value
	return s
}

func (s *Service) HealthThreshold(value int) *Service {
	s.healthThreshold = value
	return s
}

func (s *Service) MetricsEndpointPrefix(value string) *Service {
	s.metricsEndpoint = value
	return s
}

// Start checks the modules for the following interfaces and registers and/or starts:
//   Startable:
//   health.Checker:
//   Endpoint: Register the handler function of the Endpoint in the http service at prefix
func (s *Service) Start() error {
	el := protocol.NewErrorList("service: errors occured while starting: ")

	if s.healthEndpoint != "" {
		protocol.Info("service: health endpoint: %v", s.healthEndpoint)
		s.webserver.Handle(s.healthEndpoint, http.HandlerFunc(health.StatusHandler))
	} else {
		protocol.Debug("service: health endpoint disabled")
	}

	if s.metricsEndpoint != "" {
		protocol.Info("service: metrics endpoint: %v", s.metricsEndpoint)
		s.webserver.Handle(s.metricsEndpoint, http.HandlerFunc(metrics.HttpHandler))
	} else {
		protocol.Debug("service: metrics endpoint disabled")
	}

	for _, module := range s.modules {
		name := reflect.TypeOf(module).String()
		if startable, ok := module.(Startable); ok {
			protocol.Info("service: starting module %v", name)
			if err := startable.Start(); err != nil {
				protocol.Err("service: error while starting module %v", name)
				el.Add(err)
			}
		}
		if checker, ok := module.(health.Checker); ok && s.healthEndpoint != "" {
			protocol.Info("service: registering module %v as HealthChecker", name)
			health.RegisterPeriodicThresholdFunc(name, s.healthFrequency, s.healthThreshold, health.CheckFunc(checker.Check))
		}
		if endpoint, ok := module.(Endpoint); ok {
			prefix := endpoint.GetPrefix()
			protocol.Info("service: registering module %v as Endpoint to %v", name, prefix)
			s.webserver.Handle(prefix, endpoint)
		}
	}
	return el.ErrorOrNil()
}

// Stop stops the registered modules in the required order
func (s *Service) Stop() error {
	var stopables []Stopable

	for _, module := range s.modules {
		if stopable, ok := module.(Stopable); ok {
			stopables = append(stopables, stopable)
		}
	}

	stopOrder := make([]int, len(stopables))
	for i := 1; i < len(stopables); i++ {
		stopOrder[i] = len(stopables) - i
	}

	protocol.Debug("service: stopping %d modules in this order relative to registration: %v", len(stopOrder), stopOrder)

	errors := protocol.NewErrorList("stopping errors: ")
	for _, order := range stopOrder {
		module := stopables[order]
		name := reflect.TypeOf(module).String()

		protocol.Info("service: stopping [%d] %v", order, name)
		if err := module.Stop(); err != nil {
			errors.Add(err)
		}
	}

	if err := errors.ErrorOrNil(); err != nil {
		return fmt.Errorf("service: errors while stopping modules: %s", err)
	}

	return nil
}

// Modules returns the slice of registered modules
func (s *Service) Modules() []interface{} {
	return s.modules
}

// WebServer returns the service *webserver.WebServer instance
func (s *Service) WebServer() *webserver.WebServer {
	return s.webserver
}
<commit_msg>removed unused setters<commit_after>package server

import (
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/webserver"

	"github.com/docker/distribution/health"

	"fmt"
	"github.com/smancke/guble/metrics"
	"net/http"
	"reflect"
	"time"
)

const (
	defaultHealthFrequency = time.Second * 60
	defaultHealthThreshold = 1
)

// Startable interface for modules which provide a start mechanism
type Startable interface {
	Start() error
}

// Stopable interface for modules which provide a stop mechanism
type Stopable interface {
	Stop() error
}

// Endpoint adds a HTTP handler for the `GetPrefix()` to the webserver
type Endpoint interface {
	http.Handler
	GetPrefix() string
}

// Service is the main class for simple control of a server
type Service struct {
	webserver       *webserver.WebServer
	router          Router
	modules         []interface{}
	healthEndpoint  string
	healthFrequency time.Duration
	healthThreshold int
	metricsEndpoint string
}

// NewService registers the Main Router, where other modules can subscribe for messages
func NewService(router Router, webserver *webserver.WebServer) *Service {
	service := &Service{
		webserver:       webserver,
		router:          router,
		healthFrequency: defaultHealthFrequency,
		healthThreshold: defaultHealthThreshold,
	}
	service.RegisterModules(service.router, service.webserver)
	return service
}

func (s *Service) RegisterModules(modules ...interface{}) {
	protocol.Debug("service: RegisterModules: adding %d modules after existing %d modules", len(modules), len(s.modules))
	s.modules = append(s.modules, modules...)
}

func (s *Service) HealthEndpointPrefix(value string) *Service {
	s.healthEndpoint = value
	return s
}

func (s *Service) MetricsEndpointPrefix(value string) *Service {
	s.metricsEndpoint = value
	return s
}

// Start checks the modules for the following interfaces and registers and/or starts:
//   Startable:
//   health.Checker:
//   Endpoint: Register the handler function of the Endpoint in the http service at prefix
func (s *Service) Start() error {
	el := protocol.NewErrorList("service: errors occured while starting: ")

	if s.healthEndpoint != "" {
		protocol.Info("service: health endpoint: %v", s.healthEndpoint)
		s.webserver.Handle(s.healthEndpoint, http.HandlerFunc(health.StatusHandler))
	} else {
		protocol.Debug("service: health endpoint disabled")
	}

	if s.metricsEndpoint != "" {
		protocol.Info("service: metrics endpoint: %v", s.metricsEndpoint)
		s.webserver.Handle(s.metricsEndpoint, http.HandlerFunc(metrics.HttpHandler))
	} else {
		protocol.Debug("service: metrics endpoint disabled")
	}

	for _, module := range s.modules {
		name := reflect.TypeOf(module).String()
		if startable, ok := module.(Startable); ok {
			protocol.Info("service: starting module %v", name)
			if err := startable.Start(); err != nil {
				protocol.Err("service: error while starting module %v", name)
				el.Add(err)
			}
		}
		if checker, ok := module.(health.Checker); ok && s.healthEndpoint != "" {
			protocol.Info("service: registering module %v as HealthChecker", name)
			health.RegisterPeriodicThresholdFunc(name, s.healthFrequency, s.healthThreshold, health.CheckFunc(checker.Check))
		}
		if endpoint, ok := module.(Endpoint); ok {
			prefix := endpoint.GetPrefix()
			protocol.Info("service: registering module %v as Endpoint to %v", name, prefix)
			s.webserver.Handle(prefix, endpoint)
		}
	}
	return el.ErrorOrNil()
}

// Stop stops the registered modules in the required order
func (s *Service) Stop() error {
	var stopables []Stopable

	for _, module := range s.modules {
		if stopable, ok := module.(Stopable); ok {
			stopables = append(stopables, stopable)
		}
	}

	stopOrder := make([]int, len(stopables))
	for i := 1; i < len(stopables); i++ {
		stopOrder[i] = len(stopables) - i
	}

	protocol.Debug("service: stopping %d modules in this order relative to registration: %v", len(stopOrder), stopOrder)

	errors := protocol.NewErrorList("stopping errors: ")
	for _, order := range stopOrder {
		module := stopables[order]
		name := reflect.TypeOf(module).String()

		protocol.Info("service: stopping [%d] %v", order, name)
		if err := module.Stop(); err != nil {
			errors.Add(err)
		}
	}

	if err := errors.ErrorOrNil(); err != nil {
		return fmt.Errorf("service: errors while stopping modules: %s", err)
	}

	return nil
}

// Modules returns the slice of registered modules
func (s *Service) Modules() []interface{} {
	return s.modules
}

// WebServer returns the service *webserver.WebServer instance
func (s *Service) WebServer() *webserver.WebServer {
	return s.webserver
}
<|endoftext|>


<commit_before>package ui

import (
	"github.com/jroimartin/gocui"
)

// nextView is shared between Playlists and Queue and they all go to Tracks
func nextView(g *gocui.Gui, v *gocui.View) error {
	return gui.enableTracksView()
}

func mainNextViewLeft(g *gocui.Gui, v *gocui.View) error {
	return gui.enableSideView()
}

func mainNextViewRight(g *gocui.Gui, v *gocui.View) error {
	return gui.enableQueueView()
}

func cursorEnd(g *gocui.Gui, v *gocui.View) error {
	if newIndex := getCurrentViewSize(v); newIndex > -1 {
		ox, _ := v.Origin()
		cx, _ := v.Cursor()
		_, sizeY := v.Size()
		sizeY--

		if newIndex > sizeY {
			v.SetOrigin(ox, newIndex-sizeY)
			v.SetCursor(cx, sizeY)
		} else {
			v.SetCursor(cx, newIndex)
		}

		updateTracksView(g, v)
	}
	return nil
}

func cursorHome(g *gocui.Gui, v *gocui.View) error {
	ox, _ := v.Origin()
	cx, _ := v.Cursor()
	v.SetCursor(cx, 0)
	v.SetOrigin(ox, 0)

	updateTracksView(g, v)
	return nil
}

func cursorPgup(g *gocui.Gui, v *gocui.View) error {
	ox, oy := v.Origin()
	cx, cy := v.Cursor()
	_, pageSizeY := v.Size()
	pageSizeY--

	if newOriginY := oy - pageSizeY; newOriginY > 0 {
		v.SetOrigin(ox, newOriginY)
		v.SetCursor(cx, cy)
	} else {
		v.SetOrigin(ox, 0)
		v.SetCursor(cx, cy)
	}
	updateTracksView(g, v)
	return nil
}

func cursorPgdn(g *gocui.Gui, v *gocui.View) error {
	if maxSize := getCurrentViewSize(v); maxSize > -1 {
		ox, oy := v.Origin()
		cx, cy := v.Cursor()
		_, pageSizeY := v.Size()
		pageSizeY--

		newOriginY := oy + pageSizeY

		if hasMorePages(newOriginY, cy, maxSize) {
			v.SetOrigin(ox, newOriginY)
			v.SetCursor(cx, cy)
		} else if isNotInLastPage(oy, pageSizeY, maxSize) {
			v.SetOrigin(ox, maxSize-pageSizeY)
			v.SetCursor(cx, pageSizeY)
		}
		updateTracksView(g, v)
	}
	return nil
}

func updateTracksView(g *gocui.Gui, v *gocui.View) {
	if v == gui.playlistsView {
		gui.updateTracksView()
	}
}

func getCurrentViewSize(v *gocui.View) int {
	if v == gui.tracksView {
		return getTracksViewSize(v)
	} else if v == gui.playlistsView {
		return getPlaylistsViewSize(v)
	}
	return -1
}

func getTracksViewSize(v *gocui.View) int {
	if selectedPlaylist := gui.getSelectedPlaylist(); selectedPlaylist != nil {
		return selectedPlaylist.Tracks() - 1
	}
	return -1
}

func getPlaylistsViewSize(v *gocui.View) int {
	subPlaylists := 0
	for _, key := range playlists.Names() {
		playlist := playlists.Get(key)
		if playlist.IsFolder() && playlist.IsFolderOpen() {
			subPlaylists += playlist.Playlists()
		}
	}
	return playlists.Playlists() + subPlaylists - 1
}

func hasMorePages(newOriginY int, cursorY int, maxSize int) bool {
	return newOriginY+cursorY <= maxSize
}

func isNotInLastPage(originY int, pageSizeY int, maxSize int) bool {
	return originY+pageSizeY <= maxSize
}

func cursorDown(g *gocui.Gui, v *gocui.View) error {
	offset := getOffsetFromTypedNumbers()
	if cx, cy := v.Cursor(); canGoToNewPosition(cy + offset) {
		if err := v.SetCursor(cx, cy+offset); err != nil {
			ox, oy := v.Origin()
			if err := v.SetOrigin(ox, oy+offset); err != nil {
				return err
			}
		}
		if v == gui.playlistsView {
			gui.updateTracksView()
		}
	}
	return nil
}

func cursorUp(g *gocui.Gui, v *gocui.View) error {
	offset := getOffsetFromTypedNumbers()
	ox, oy := v.Origin()
	cx, cy := v.Cursor()
	if err := v.SetCursor(cx, cy-offset); err != nil && oy > 0 {
		if err := v.SetOrigin(ox, oy-offset); err != nil {
			return err
		}
	}
	if v == gui.playlistsView {
		gui.updateTracksView()
	}
	return nil
}

func getOffsetFromTypedNumbers() int {
	if multipleKeysNumber > 1 {
		return multipleKeysNumber
	}
	return 1
}

func canGoToNewPosition(newPosition int) bool {
	currentView := gui.g.CurrentView()
	line, err := currentView.Line(newPosition)
	if err != nil || len(line) == 0 {
		return false
	}
	return true
}

func canGoToAbsoluteNewPosition(v *gocui.View, newPosition int) bool {
	switch v {
	case gui.playlistsView:
		return newPosition <= playlists.Playlists()
	case gui.tracksView:
		if currentPlaylist := gui.getSelectedPlaylist(); currentPlaylist != nil {
			return newPosition <= currentPlaylist.Tracks()
		}
	case gui.queueView:
	}
	return true
}

func goTo(g *gocui.Gui, v *gocui.View, position int) error {
	if canGoToAbsoluteNewPosition(v, position) {
		position--
		ox, _ := v.Origin()
		cx, _ := v.Cursor()
		v.SetCursor(cx, 0)
		v.SetOrigin(ox, 0)
		if err := v.SetCursor(cx, position); err != nil {
			if err := v.SetOrigin(ox, position); err != nil {
				return err
			}
		}
		if v == gui.playlistsView && gui.tracksView != nil {
			gui.updateTracksView()
		}
	}
	return nil
}

func goToFirstLineCommand(g *gocui.Gui, v *gocui.View) error {
	if multipleKeysNumber <= 0 {
		return cursorHome(g, v)
	}

	return goTo(g, v, multipleKeysNumber)
}

func goToLastLineCommand(g *gocui.Gui, v *gocui.View) error {
	if multipleKeysNumber <= 0 {
		return cursorEnd(g, v)
	}

	return goTo(g, v, multipleKeysNumber)
}
<commit_msg>When on demand playlist return size + 1<commit_after>package ui

import (
	"github.com/jroimartin/gocui"
)

// nextView is shared between Playlists and Queue and they all go to Tracks
func nextView(g *gocui.Gui, v *gocui.View) error {
	return gui.enableTracksView()
}

func mainNextViewLeft(g *gocui.Gui, v *gocui.View) error {
	return gui.enableSideView()
}

func mainNextViewRight(g *gocui.Gui, v *gocui.View) error {
	return gui.enableQueueView()
}

func cursorEnd(g *gocui.Gui, v *gocui.View) error {
	if newIndex := getCurrentViewSize(v); newIndex > -1 {
		ox, _ := v.Origin()
		cx, _ := v.Cursor()
		_, sizeY := v.Size()
		sizeY--

		if newIndex > sizeY {
			v.SetOrigin(ox, newIndex-sizeY)
			v.SetCursor(cx, sizeY)
		} else {
			v.SetCursor(cx, newIndex)
		}

		updateTracksView(g, v)
	}
	return nil
}

func cursorHome(g *gocui.Gui, v *gocui.View) error {
	ox, _ := v.Origin()
	cx, _ := v.Cursor()
	v.SetCursor(cx, 0)
	v.SetOrigin(ox, 0)

	updateTracksView(g, v)
	return nil
}

func cursorPgup(g *gocui.Gui, v *gocui.View) error {
	ox, oy := v.Origin()
	cx, cy := v.Cursor()
	_, pageSizeY := v.Size()
	pageSizeY--

	if newOriginY := oy - pageSizeY; newOriginY > 0 {
		v.SetOrigin(ox, newOriginY)
		v.SetCursor(cx, cy)
	} else {
		v.SetOrigin(ox, 0)
		v.SetCursor(cx, cy)
	}
	updateTracksView(g, v)
	return nil
}

func cursorPgdn(g *gocui.Gui, v *gocui.View) error {
	if maxSize := getCurrentViewSize(v); maxSize > -1 {
		ox, oy := v.Origin()
		cx, cy := v.Cursor()
		_, pageSizeY := v.Size()
		pageSizeY--

		newOriginY := oy + pageSizeY

		if hasMorePages(newOriginY, cy, maxSize) {
			v.SetOrigin(ox, newOriginY)
			v.SetCursor(cx, cy)
		} else if isNotInLastPage(oy, pageSizeY, maxSize) {
			v.SetOrigin(ox, maxSize-pageSizeY)
			v.SetCursor(cx, pageSizeY)
		}
		updateTracksView(g, v)
	}
	return nil
}

func updateTracksView(g *gocui.Gui, v *gocui.View) {
	if v == gui.playlistsView {
		gui.updateTracksView()
	}
}

func getCurrentViewSize(v *gocui.View) int {
	if v == gui.tracksView {
		return getTracksViewSize(v)
	} else if v == gui.playlistsView {
		return getPlaylistsViewSize(v)
	}
	return -1
}

func getTracksViewSize(v *gocui.View) int {
	if selectedPlaylist := gui.getSelectedPlaylist(); selectedPlaylist != nil {
		if selectedPlaylist.IsOnDemand() {
			return selectedPlaylist.Tracks()
		}
		return selectedPlaylist.Tracks() - 1
	}
	return -1
}

func getPlaylistsViewSize(v *gocui.View) int {
	subPlaylists := 0
	for _, key := range playlists.Names() {
		playlist := playlists.Get(key)
		if playlist.IsFolder() && playlist.IsFolderOpen() {
			subPlaylists += playlist.Playlists()
		}
	}
	return playlists.Playlists() + subPlaylists - 1
}

func hasMorePages(newOriginY int, cursorY int, maxSize int) bool {
	return newOriginY+cursorY <= maxSize
}

func isNotInLastPage(originY int, pageSizeY int, maxSize int) bool {
	return originY+pageSizeY <= maxSize
}

func cursorDown(g *gocui.Gui, v *gocui.View) error {
	offset := getOffsetFromTypedNumbers()
	if cx, cy := v.Cursor(); canGoToNewPosition(cy + offset) {
		if err := v.SetCursor(cx, cy+offset); err != nil {
			ox, oy := v.Origin()
			if err := v.SetOrigin(ox, oy+offset); err != nil {
				return err
			}
		}
		if v == gui.playlistsView {
			gui.updateTracksView()
		}
	}
	return nil
}

func cursorUp(g *gocui.Gui, v *gocui.View) error {
	offset := getOffsetFromTypedNumbers()
	ox, oy := v.Origin()
	cx, cy := v.Cursor()
	if err := v.SetCursor(cx, cy-offset); err != nil && oy > 0 {
		if err := v.SetOrigin(ox, oy-offset); err != nil {
			return err
		}
	}
	if v == gui.playlistsView {
		gui.updateTracksView()
	}
	return nil
}

func getOffsetFromTypedNumbers() int {
	if multipleKeysNumber > 1 {
		return multipleKeysNumber
	}
	return 1
}

func canGoToNewPosition(newPosition int) bool {
	currentView := gui.g.CurrentView()
	line, err := currentView.Line(newPosition)
	if err != nil || len(line) == 0 {
		return false
	}
	return true
}

func canGoToAbsoluteNewPosition(v *gocui.View, newPosition int) bool {
	switch v {
	case gui.playlistsView:
		return newPosition <= playlists.Playlists()
	case gui.tracksView:
		if currentPlaylist := gui.getSelectedPlaylist(); currentPlaylist != nil {
			return newPosition <= currentPlaylist.Tracks()
		}
	case gui.queueView:
	}
	return true
}

func goTo(g *gocui.Gui, v *gocui.View, position int) error {
	if canGoToAbsoluteNewPosition(v, position) {
		position--
		ox, _ := v.Origin()
		cx, _ := v.Cursor()
		v.SetCursor(cx, 0)
		v.SetOrigin(ox, 0)
		if err := v.SetCursor(cx, position); err != nil {
			if err := v.SetOrigin(ox, position); err != nil {
				return err
			}
		}
		if v == gui.playlistsView && gui.tracksView != nil {
			gui.updateTracksView()
		}
	}
	return nil
}

func goToFirstLineCommand(g *gocui.Gui, v *gocui.View) error {
	if multipleKeysNumber <= 0 {
		return cursorHome(g, v)
	}

	return goTo(g, v, multipleKeysNumber)
}

func goToLastLineCommand(g *gocui.Gui, v *gocui.View) error {
	if multipleKeysNumber <= 0 {
		return cursorEnd(g, v)
	}

	return goTo(g, v, multipleKeysNumber)
}
<|endoftext|>


<commit_before>// Copyright 2015 Robert S. Gerus. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package bot

import (
	"encoding/json"
	"fmt"
	"golang.org/x/text/encoding/charmap"
	"golang.org/x/text/transform"
	"regexp"
	"strings"
	"unicode/utf8"

	"github.com/arachnist/gorepost/irc"
)

var trimTitle = regexp.MustCompile("[\\s]+")
var trimLink = regexp.MustCompile("^.*?http")
var enc = charmap.ISO8859_2

func youtube(vid string) string {
	var dat map[string]interface{}
	link := fmt.Sprintf("https://www.youtube.com/oembed?format=json&url=http://www.youtube.com/watch?v=%+v", vid)
	data, err := httpGet(link)
	if err != nil {
		return "error getting data from youtube"
	}

	err = json.Unmarshal(data, &dat)
	if err != nil {
		return "error decoding data from youtube"
	}
	return dat["title"].(string)
}

func youtubeLong(l string) string {
	pattern := regexp.MustCompile(`/watch[?]v[=](?P<vid>[a-zA-Z0-9-_]+)`)
	res := []byte{}
	for _, s := range pattern.FindAllSubmatchIndex([]byte(l), -1) {
		res = pattern.ExpandString(res, "$vid", l, s)
	}
	return youtube(string(res))
}

func youtubeShort(l string) string {
	pattern := regexp.MustCompile(`youtu.be/(?P<vid>[a-zA-Z0-9-_]+)`)
	res := []byte{}
	for _, s := range pattern.FindAllSubmatchIndex([]byte(l), -1) {
		res = pattern.ExpandString(res, "$vid", l, s)
	}
	return youtube(string(res))
}

func genericURLTitle(l string) string {
	title, err := httpGetXpath(l, "//head/title")
	if err == errElementNotFound {
		return "no title"
	} else if err != nil {
		return fmt.Sprint("error:", err)
	}

	title = string(trimTitle.ReplaceAll([]byte(title), []byte{' '})[:])
	if !utf8.ValidString(title) {
		title, _, err = transform.String(enc.NewDecoder(), title)
		if err != nil {
			return fmt.Sprint("error:", err)
		}
	}

	return title
}

var customDataFetchers = []struct {
	re      *regexp.Regexp
	fetcher func(l string) string
}{
	{
		re:      regexp.MustCompile("//(www.)?youtube.com/"),
		fetcher: youtubeLong,
	},
	{
		re:      regexp.MustCompile("//youtu.be/"),
		fetcher: youtubeShort,
	},
	{
		re:      regexp.MustCompile(".*"),
		fetcher: genericURLTitle,
	},
}

func linktitle(output func(irc.Message), msg irc.Message) {
	var r []string

	for _, s := range strings.Split(strings.Trim(msg.Trailing, "\001"), " ") {
		if s == "notitle" {
			return
		}

		b, _ := regexp.Match("https?://", []byte(s))

		s = string(trimLink.ReplaceAll([]byte(s), []byte("http"))[:])

		if b {
		FetchersLoop:
			for _, d := range customDataFetchers {
				if d.re.MatchString(s) {
					t := d.fetcher(s)
					if t != "no title" {
						r = append(r, t)
					}
					break FetchersLoop
				}
			}
		}
	}

	if len(r) > 0 {
		t := cfg.LookupString(msg.Context, "LinkTitlePrefix") + strings.Join(r, cfg.LookupString(msg.Context, "LinkTitleDelimiter"))

		output(reply(msg, t))
	}
}

func init() {
	addCallback("PRIVMSG", "LINKTITLE", linktitle)
}
<commit_msg>Regexp fixup.<commit_after>// Copyright 2015 Robert S. Gerus. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package bot

import (
	"encoding/json"
	"fmt"
	"golang.org/x/text/encoding/charmap"
	"golang.org/x/text/transform"
	"regexp"
	"strings"
	"unicode/utf8"

	"github.com/arachnist/gorepost/irc"
)

var trimTitle = regexp.MustCompile("[\\s]+")
var trimLink = regexp.MustCompile("^.*?http")
var enc = charmap.ISO8859_2

func youtube(vid string) string {
	var dat map[string]interface{}
	link := fmt.Sprintf("https://www.youtube.com/oembed?format=json&url=http://www.youtube.com/watch?v=%+v", vid)
	data, err := httpGet(link)
	if err != nil {
		return "error getting data from youtube"
	}

	err = json.Unmarshal(data, &dat)
	if err != nil {
		return "error decoding data from youtube"
	}
	return dat["title"].(string)
}

func youtubeLong(l string) string {
	pattern := regexp.MustCompile(`/watch[?]v[=](?P<vid>[a-zA-Z0-9-_]+)`)
	res := []byte{}
	for _, s := range pattern.FindAllSubmatchIndex([]byte(l), -1) {
		res = pattern.ExpandString(res, "$vid", l, s)
	}
	return youtube(string(res))
}

func youtubeShort(l string) string {
	pattern := regexp.MustCompile(`youtu.be/(?P<vid>[a-zA-Z0-9-_]+)`)
	res := []byte{}
	for _, s := range pattern.FindAllSubmatchIndex([]byte(l), -1) {
		res = pattern.ExpandString(res, "$vid", l, s)
	}
	return youtube(string(res))
}

func genericURLTitle(l string) string {
	title, err := httpGetXpath(l, "//head/title")
	if err == errElementNotFound {
		return "no title"
	} else if err != nil {
		return fmt.Sprint("error:", err)
	}

	title = string(trimTitle.ReplaceAll([]byte(title), []byte{' '})[:])
	if !utf8.ValidString(title) {
		title, _, err = transform.String(enc.NewDecoder(), title)
		if err != nil {
			return fmt.Sprint("error:", err)
		}
	}

	return title
}

var customDataFetchers = []struct {
	re      *regexp.Regexp
	fetcher func(l string) string
}{
	{
		re:      regexp.MustCompile("//(www.)?youtube.com/watch"),
		fetcher: youtubeLong,
	},
	{
		re:      regexp.MustCompile("//youtu.be/"),
		fetcher: youtubeShort,
	},
	{
		re:      regexp.MustCompile(".*"),
		fetcher: genericURLTitle,
	},
}

func linktitle(output func(irc.Message), msg irc.Message) {
	var r []string

	for _, s := range strings.Split(strings.Trim(msg.Trailing, "\001"), " ") {
		if s == "notitle" {
			return
		}

		b, _ := regexp.Match("https?://", []byte(s))

		s = string(trimLink.ReplaceAll([]byte(s), []byte("http"))[:])

		if b {
		FetchersLoop:
			for _, d := range customDataFetchers {
				if d.re.MatchString(s) {
					t := d.fetcher(s)
					if t != "no title" {
						r = append(r, t)
					}
					break FetchersLoop
				}
			}
		}
	}

	if len(r) > 0 {
		t := cfg.LookupString(msg.Context, "LinkTitlePrefix") + strings.Join(r, cfg.LookupString(msg.Context, "LinkTitleDelimiter"))

		output(reply(msg, t))
	}
}

func init() {
	addCallback("PRIVMSG", "LINKTITLE", linktitle)
}
<|endoftext|>


<commit_before>package bot

import (
	"bytes"
	"encoding/base32"
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	otp "github.com/dgryski/dgoogauth"
	"github.com/uva-its/yaml"
)

// if help is more than tooLong lines long, send a private message
const tooLong = 14

// Size of QR code
const qrsize = 400

// If this list doesn't match what's registered below,
// you're gonna have a bad time.
var builtIns = []string{
	"builtInhelp",
	"builtInadmin",
	"builtIndump",
	"builtInlaunchcodes",
}

// To make help output more readable
var helpRe *regexp.Regexp

func init() {
	RegisterPlugin("builtIndump", PluginHandler{DefaultConfig: dumpConfig, Handler: dump})
	RegisterPlugin("builtInhelp", PluginHandler{DefaultConfig: helpConfig, Handler: help})
	RegisterPlugin("builtInadmin", PluginHandler{DefaultConfig: adminConfig, Handler: admin})
	RegisterPlugin("builtInlaunchcodes", PluginHandler{DefaultConfig: launchCodesConfig, Handler: launchCode})

	helpRe = regexp.MustCompile(`\(bot\)`)
}

/* builtin plugins, like help */

func launchCode(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	var userOTP otp.OTPConfig
	otpKey := "bot:OTP:" + bot.User
	updated := false
	lock, exists, ret := checkoutDatum(otpKey, &userOTP, true)
	if ret != Ok {
		bot.Say("Yikes - something went wrong with my brain, have somebody check my log")
		return
	}
	defer func() {
		if updated {
			ret := updateDatum(otpKey, lock, &userOTP)
			if ret != Ok {
				Log(Error, "Couldn't save OTP config")
				bot.Reply("Good grief. I'm having trouble remembering your launch codes - have somebody check my log")
			}
		} else {
			// Well-behaved plugins will always do a CheckinDatum when the datum hasn't been updated,
			// in case there's another thread waiting.
			checkinDatum(otpKey, lock)
		}
	}()
	switch command {
	case "send":
		if exists {
			bot.Reply("I've already sent you the launch codes, contact an administrator if you're having problems")
			return
		}
		otpb := make([]byte, 10)
		random.Read(otpb)
		userOTP.Secret = base32.StdEncoding.EncodeToString(otpb)
		userOTP.WindowSize = 2
		userOTP.DisallowReuse = []int{}
		var codeMail bytes.Buffer
		fmt.Fprintf(&codeMail, "For your authenticator:\n%s\n", userOTP.Secret)
		if ret := bot.Email("Your launch codes - if you print this email, please chew it up and swallow it", &codeMail); ret != Ok {
			bot.Reply("There was a problem sending your launch codes, contact an administrator")
			return
		}
		updated = true
		bot.Reply("I've emailed your launch codes - please delete it promptly")
	case "check":
		if !exists {
			bot.Reply("It doesn't appear you've been issued any launch codes, try 'send launch codes'")
			return
		}
		valid, err := userOTP.Authenticate(args[0])
		if err != nil {
			Log(Error, fmt.Errorf("Problem authenticating launch code: %v", err))
			bot.Reply("There was an error authenticating your launch code, have an adminstrator check the log")
			return
		}
		// Whether valid or not, the otp lib may update the struct
		updated = true
		if valid {
			bot.Reply("The launch code was valid")
		} else {
			bot.Reply("You supplied an invalid launch code, and I've contacted POTUS and the NSA")
		}
	}
}

func help(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	if command == "help" {
		b.lock.RLock()
		defer b.lock.RUnlock()

		var term, helpOutput string
		hasTerm := false
		helpLines := 0
		if len(args) == 1 && len(args[0]) > 0 {
			hasTerm = true
			term = args[0]
			if term == "help" {
				Log(Trace, "Help requested for help, returning")
				return
			}
			Log(Trace, "Help requested for term", term)
		}

		for _, plugin := range plugins {
			Log(Trace, fmt.Sprintf("Checking help for plugin %s (term: %s)", plugin.name, term))
			if !hasTerm { // if you ask for help without a term, you just get help for whatever commands are available to you
				if messageAppliesToPlugin(bot.User, bot.Channel, plugin) {
					for _, phelp := range plugin.Help {
						for _, helptext := range phelp.Helptext {
							if phelp.Keywords[0] == "*" {
								// * signifies help that should be prepended
								helpOutput = helpRe.ReplaceAllString(helptext, b.name) + string('\n') + helpOutput
							} else {
								helpOutput += helpRe.ReplaceAllString(helptext, b.name) + string('\n')
							}
							helpLines++
						}
					}
				}
			} else { // when there's a search term, give all help for that term, but add (channels: xxx) at the end
				for _, phelp := range plugin.Help {
					for _, keyword := range phelp.Keywords {
						if term == keyword {
							chantext := ""
							if plugin.DirectOnly {
								// Look: the right paren gets added below
								chantext = " (direct message only"
							} else {
								for _, pchan := range plugin.Channels {
									if len(chantext) == 0 {
										chantext += " (channels: " + pchan
									} else {
										chantext += ", " + pchan
									}
								}
							}
							if len(chantext) != 0 {
								chantext += ")"
							}
							for _, helptext := range phelp.Helptext {
								helpOutput += helptext + chantext + string('\n')
								helpLines++
							}
						}
					}
				}
			}
		}
		switch {
		case helpLines == 0:
			bot.Say("Sorry, bub - I got nothin' for ya'")
		case helpLines > tooLong:
			if len(bot.Channel) > 0 {
				bot.Reply("(the help output was pretty long, so I sent you a private message)")
				if hasTerm {
					helpOutput = "Help for term: " + term + "\n" + helpOutput
				} else {
					helpOutput = "Help for commands available in channel: " + bot.Channel + "\n" + helpOutput
				}
			}
			bot.SendUserMessage(bot.User, strings.TrimRight(helpOutput, "\n"))
		default:
			bot.Say(strings.TrimRight(helpOutput, "\n"))
		}
	}
}

func dump(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	b.lock.RLock()
	defer b.lock.RUnlock()
	switch command {
	case "robotdefault":
		bot.Fixed().Say(fmt.Sprintf("Here's my default configuration:\n%s", defaultConfig))
	case "robot":
		c, _ := yaml.Marshal(config)
		bot.Fixed().Say(fmt.Sprintf("Here's how I've been configured, irrespective of interactive changes:\n%s", c))
	case "plugdefault":
		if plug, ok := pluginHandlers[args[0]]; ok {
			bot.Fixed().Say(fmt.Sprintf("Here's the default configuration for \"%s\":\n%s", args[0], plug.DefaultConfig))
		} else { // look for an external plugin
			found := false
			for _, plugin := range plugins {
				if args[0] == plugin.name && plugin.pluginType == plugExternal {
					found = true
					if cfg, err := getExtDefCfg(plugin); err == nil {
						bot.Fixed().Say(fmt.Sprintf("Here's the default configuration for \"%s\":\n%s", args[0], *cfg))
					} else {
						bot.Say("I had a problem looking that up - somebody should check my logs")
					}
				}
			}
			if !found {
				bot.Say("Didn't find a plugin named " + args[0])
			}
		}
	case "plugin":
		found := false
		for _, plugin := range plugins {
			if args[0] == plugin.name {
				found = true
				c, _ := yaml.Marshal(plugin)
				bot.Fixed().Say(fmt.Sprintf("%s", c))
			}
		}
		if !found {
			bot.Say("Didn't find a plugin named " + args[0])
		}
	case "list":
		plist := make([]string, 0, len(plugins))
		for _, plugin := range plugins {
			plist = append(plist, plugin.name)
		}
		bot.Say(fmt.Sprintf("Here are the plugins I have configured:\n%s", strings.Join(plist, ", ")))
	}
}

var byebye = []string{
	"Sayonara!",
	"Adios",
	"Hasta la vista!",
	"Later gator!",
}

func admin(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	if !bot.CheckAdmin() {
		bot.Reply("Sorry, only an admin user can request that")
		return
	}
	switch command {
	case "reload":
		err := loadConfig()
		if err != nil {
			bot.Reply("Error encountered during reload, check the logs")
			Log(Error, fmt.Errorf("Reloading configuration, requested by %s: %v", bot.User, err))
			return
		}
		bot.Reply("Configuration reloaded successfully")
		Log(Info, "Configuration successfully reloaded by a request from:", bot.User)
	case "quit":
		// Get all important locks to make sure nothing is being changed - then exit
		botLock.Lock()
		b.lock.Lock()
		dataLock.Lock()
		bot.Say(bot.RandomString(byebye))
		Log(Info, "Exiting on administrator command")
		// How long does it _actually_ take for the message to go out?
		time.Sleep(time.Second)
		os.Exit(0)
	}
}
<commit_msg>Make sure there are keywords before checking one<commit_after>package bot

import (
	"bytes"
	"encoding/base32"
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	otp "github.com/dgryski/dgoogauth"
	"github.com/uva-its/yaml"
)

// if help is more than tooLong lines long, send a private message
const tooLong = 14

// Size of QR code
const qrsize = 400

// If this list doesn't match what's registered below,
// you're gonna have a bad time.
var builtIns = []string{
	"builtInhelp",
	"builtInadmin",
	"builtIndump",
	"builtInlaunchcodes",
}

// To make help output more readable
var helpRe *regexp.Regexp

func init() {
	RegisterPlugin("builtIndump", PluginHandler{DefaultConfig: dumpConfig, Handler: dump})
	RegisterPlugin("builtInhelp", PluginHandler{DefaultConfig: helpConfig, Handler: help})
	RegisterPlugin("builtInadmin", PluginHandler{DefaultConfig: adminConfig, Handler: admin})
	RegisterPlugin("builtInlaunchcodes", PluginHandler{DefaultConfig: launchCodesConfig, Handler: launchCode})

	helpRe = regexp.MustCompile(`\(bot\)`)
}

/* builtin plugins, like help */

func launchCode(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	var userOTP otp.OTPConfig
	otpKey := "bot:OTP:" + bot.User
	updated := false
	lock, exists, ret := checkoutDatum(otpKey, &userOTP, true)
	if ret != Ok {
		bot.Say("Yikes - something went wrong with my brain, have somebody check my log")
		return
	}
	defer func() {
		if updated {
			ret := updateDatum(otpKey, lock, &userOTP)
			if ret != Ok {
				Log(Error, "Couldn't save OTP config")
				bot.Reply("Good grief. I'm having trouble remembering your launch codes - have somebody check my log")
			}
		} else {
			// Well-behaved plugins will always do a CheckinDatum when the datum hasn't been updated,
			// in case there's another thread waiting.
			checkinDatum(otpKey, lock)
		}
	}()
	switch command {
	case "send":
		if exists {
			bot.Reply("I've already sent you the launch codes, contact an administrator if you're having problems")
			return
		}
		otpb := make([]byte, 10)
		random.Read(otpb)
		userOTP.Secret = base32.StdEncoding.EncodeToString(otpb)
		userOTP.WindowSize = 2
		userOTP.DisallowReuse = []int{}
		var codeMail bytes.Buffer
		fmt.Fprintf(&codeMail, "For your authenticator:\n%s\n", userOTP.Secret)
		if ret := bot.Email("Your launch codes - if you print this email, please chew it up and swallow it", &codeMail); ret != Ok {
			bot.Reply("There was a problem sending your launch codes, contact an administrator")
			return
		}
		updated = true
		bot.Reply("I've emailed your launch codes - please delete it promptly")
	case "check":
		if !exists {
			bot.Reply("It doesn't appear you've been issued any launch codes, try 'send launch codes'")
			return
		}
		valid, err := userOTP.Authenticate(args[0])
		if err != nil {
			Log(Error, fmt.Errorf("Problem authenticating launch code: %v", err))
			bot.Reply("There was an error authenticating your launch code, have an adminstrator check the log")
			return
		}
		// Whether valid or not, the otp lib may update the struct
		updated = true
		if valid {
			bot.Reply("The launch code was valid")
		} else {
			bot.Reply("You supplied an invalid launch code, and I've contacted POTUS and the NSA")
		}
	}
}

func help(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	if command == "help" {
		b.lock.RLock()
		defer b.lock.RUnlock()

		var term, helpOutput string
		hasTerm := false
		helpLines := 0
		if len(args) == 1 && len(args[0]) > 0 {
			hasTerm = true
			term = args[0]
			if term == "help" {
				Log(Trace, "Help requested for help, returning")
				return
			}
			Log(Trace, "Help requested for term", term)
		}

		for _, plugin := range plugins {
			Log(Trace, fmt.Sprintf("Checking help for plugin %s (term: %s)", plugin.name, term))
			if !hasTerm { // if you ask for help without a term, you just get help for whatever commands are available to you
				if messageAppliesToPlugin(bot.User, bot.Channel, plugin) {
					for _, phelp := range plugin.Help {
						for _, helptext := range phelp.Helptext {
							if len(phelp.Keywords) > 0 && phelp.Keywords[0] == "*" {
								// * signifies help that should be prepended
								helpOutput = helpRe.ReplaceAllString(helptext, b.name) + string('\n') + helpOutput
							} else {
								helpOutput += helpRe.ReplaceAllString(helptext, b.name) + string('\n')
							}
							helpLines++
						}
					}
				}
			} else { // when there's a search term, give all help for that term, but add (channels: xxx) at the end
				for _, phelp := range plugin.Help {
					for _, keyword := range phelp.Keywords {
						if term == keyword {
							chantext := ""
							if plugin.DirectOnly {
								// Look: the right paren gets added below
								chantext = " (direct message only"
							} else {
								for _, pchan := range plugin.Channels {
									if len(chantext) == 0 {
										chantext += " (channels: " + pchan
									} else {
										chantext += ", " + pchan
									}
								}
							}
							if len(chantext) != 0 {
								chantext += ")"
							}
							for _, helptext := range phelp.Helptext {
								helpOutput += helptext + chantext + string('\n')
								helpLines++
							}
						}
					}
				}
			}
		}
		switch {
		case helpLines == 0:
			bot.Say("Sorry, bub - I got nothin' for ya'")
		case helpLines > tooLong:
			if len(bot.Channel) > 0 {
				bot.Reply("(the help output was pretty long, so I sent you a private message)")
				if hasTerm {
					helpOutput = "Help for term: " + term + "\n" + helpOutput
				} else {
					helpOutput = "Help for commands available in channel: " + bot.Channel + "\n" + helpOutput
				}
			}
			bot.SendUserMessage(bot.User, strings.TrimRight(helpOutput, "\n"))
		default:
			bot.Say(strings.TrimRight(helpOutput, "\n"))
		}
	}
}

func dump(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	b.lock.RLock()
	defer b.lock.RUnlock()
	switch command {
	case "robotdefault":
		bot.Fixed().Say(fmt.Sprintf("Here's my default configuration:\n%s", defaultConfig))
	case "robot":
		c, _ := yaml.Marshal(config)
		bot.Fixed().Say(fmt.Sprintf("Here's how I've been configured, irrespective of interactive changes:\n%s", c))
	case "plugdefault":
		if plug, ok := pluginHandlers[args[0]]; ok {
			bot.Fixed().Say(fmt.Sprintf("Here's the default configuration for \"%s\":\n%s", args[0], plug.DefaultConfig))
		} else { // look for an external plugin
			found := false
			for _, plugin := range plugins {
				if args[0] == plugin.name && plugin.pluginType == plugExternal {
					found = true
					if cfg, err := getExtDefCfg(plugin); err == nil {
						bot.Fixed().Say(fmt.Sprintf("Here's the default configuration for \"%s\":\n%s", args[0], *cfg))
					} else {
						bot.Say("I had a problem looking that up - somebody should check my logs")
					}
				}
			}
			if !found {
				bot.Say("Didn't find a plugin named " + args[0])
			}
		}
	case "plugin":
		found := false
		for _, plugin := range plugins {
			if args[0] == plugin.name {
				found = true
				c, _ := yaml.Marshal(plugin)
				bot.Fixed().Say(fmt.Sprintf("%s", c))
			}
		}
		if !found {
			bot.Say("Didn't find a plugin named " + args[0])
		}
	case "list":
		plist := make([]string, 0, len(plugins))
		for _, plugin := range plugins {
			plist = append(plist, plugin.name)
		}
		bot.Say(fmt.Sprintf("Here are the plugins I have configured:\n%s", strings.Join(plist, ", ")))
	}
}

var byebye = []string{
	"Sayonara!",
	"Adios",
	"Hasta la vista!",
	"Later gator!",
}

func admin(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	if !bot.CheckAdmin() {
		bot.Reply("Sorry, only an admin user can request that")
		return
	}
	switch command {
	case "reload":
		err := loadConfig()
		if err != nil {
			bot.Reply("Error encountered during reload, check the logs")
			Log(Error, fmt.Errorf("Reloading configuration, requested by %s: %v", bot.User, err))
			return
		}
		bot.Reply("Configuration reloaded successfully")
		Log(Info, "Configuration successfully reloaded by a request from:", bot.User)
	case "quit":
		// Get all important locks to make sure nothing is being changed - then exit
		botLock.Lock()
		b.lock.Lock()
		dataLock.Lock()
		bot.Say(bot.RandomString(byebye))
		Log(Info, "Exiting on administrator command")
		// How long does it _actually_ take for the message to go out?
		time.Sleep(time.Second)
		os.Exit(0)
	}
}
<|endoftext|>