<commit_before>package main

import (
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"syscall"

	"code.google.com/p/goauth2/oauth"
	"github.com/codegangsta/cli"
	"github.com/google/go-github/github"
	"github.com/motemen/ghq/pocket"
	"github.com/motemen/ghq/utils"
)

var Commands = []cli.Command{
	commandGet,
	commandList,
	commandLook,
	commandImport,
}

var commandGet = cli.Command{
	Name:  "get",
	Usage: "Clone/sync with a remote repository",
	Description: `
    Clone a GitHub repository under ghq root direcotry. If the repository is
    already cloned to local, nothing will happen unless '-u' ('--update')
    flag is supplied, in which case 'git remote update' is executed.
    When you use '-p' option, the repository is cloned via SSH.
`,
	Action: doGet,
	Flags: []cli.Flag{
		cli.BoolFlag{"update, u", "Update local repository if cloned already"},
		cli.BoolFlag{"p", "Clone with SSH"},
		cli.BoolFlag{"shallow", "Do a shallow clone"},
	},
}

var commandList = cli.Command{
	Name:  "list",
	Usage: "List local repositories",
	Description: `
    List locally cloned repositories. If a query argument is given, only
    repositories whose names contain that query text are listed. '-e'
    ('--exact') forces the match to be an exact one (i.e. the query equals to
    _project_ or _user_/_project_) If '-p' ('--full-path') is given, the full paths
    to the repository root are printed instead of relative ones.
`,
	Action: doList,
	Flags: []cli.Flag{
		cli.BoolFlag{"exact, e", "Perform an exact match"},
		cli.BoolFlag{"full-path, p", "Print full paths"},
		cli.BoolFlag{"unique", "Print unique subpaths"},
	},
}

var commandLook = cli.Command{
	Name:  "look",
	Usage: "Look into a local repository",
	Description: `
    Look into a locally cloned repository with the shell.
`,
	Action: doLook,
}

var commandImport = cli.Command{
	Name:  "import",
	Usage: "Import repositories from other web services",
	Subcommands: []cli.Command{
		commandImportStarred,
		commandImportPocket,
	},
}

var commandImportStarred = cli.Command{
	Name:  "starred",
	Usage: "Get all starred GitHub repositories",
	Description: `
    Retrieves GitHub repositories that are starred by the user specified and
    performs 'get' for each of them.
`,
	Action: doImportStarred,
	Flags: []cli.Flag{
		cli.BoolFlag{"update, u", "Update local repository if cloned already"},
		cli.BoolFlag{"p", "Clone with SSH"},
		cli.BoolFlag{"shallow", "Do a shallow clone"},
	},
}

var commandImportPocket = cli.Command{
	Name:  "pocket",
	Usage: "Get all github.com entries in Pocket",
	Description: `
    Retrieves Pocket <http://getpocket.com/> entries of github.com and
    performs 'get' for each of them.
`,
	Action: doImportPocket,
	Flags: []cli.Flag{
		cli.BoolFlag{"update, u", "Update local repository if cloned already"},
	},
}

type commandDoc struct {
	Parent    string
	Arguments string
}

var commandDocs = map[string]commandDoc{
	"get":     {"", "[-u] <repository URL> | [-u] [-p] <user>/<project>"},
	"list":    {"", "[-p] [-e] [<query>]"},
	"look":    {"", "<project> | <user>/<project> | <host>/<user>/<project>"},
	"import":  {"", "[-u] [-p] starred <user> | [-u] pocket"},
	"starred": {"import", "[-u] [-p] <user>"},
	"pocket":  {"import", "[-u]"},
}

// Makes template conditionals to generate per-command documents.
func mkCommandsTemplate(genTemplate func(commandDoc) string) string {
	template := "{{if false}}"
	for _, command := range append(Commands, commandImportStarred, commandImportPocket) {
		template = template + fmt.Sprintf("{{else if (eq .Name %q)}}%s", command.Name, genTemplate(commandDocs[command.Name]))
	}
	return template + "{{end}}"
}

func init() {
	argsTemplate := mkCommandsTemplate(func(doc commandDoc) string { return doc.Arguments })
	parentTemplate := mkCommandsTemplate(func(doc commandDoc) string { return string(strings.TrimLeft(doc.Parent+" ", " ")) })

	cli.CommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}

USAGE:
    ghq ` + parentTemplate + `{{.Name}} ` + argsTemplate + `
{{if (len .Description)}}
DESCRIPTION: {{.Description}}
{{end}}{{if (len .Flags)}}
OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}
{{end}}`
}

func doGet(c *cli.Context) {
	argURL := c.Args().Get(0)
	doUpdate := c.Bool("update")
	isShallow := c.Bool("shallow")

	if argURL == "" {
		cli.ShowCommandHelp(c, "get")
		os.Exit(1)
	}

	url, err := NewURL(argURL)
	utils.DieIf(err)

	isSSH := c.Bool("p")
	if isSSH {
		// Assume Git repository if `-p` is given.
		url, err = ConvertGitURLHTTPToSSH(url)
		utils.DieIf(err)
	}

	remote, err := NewRemoteRepository(url)
	utils.DieIf(err)

	if remote.IsValid() == false {
		utils.Log("error", fmt.Sprintf("Not a valid repository: %s", url))
		os.Exit(1)
	}

	getRemoteRepository(remote, doUpdate, isShallow)
}

// getRemoteRepository clones or updates a remote repository remote.
// If doUpdate is true, updates the locally cloned repository. Otherwise does nothing.
// If isShallow is true, does shallow cloning. (no effect if already cloned or the VCS is Mercurial)
func getRemoteRepository(remote RemoteRepository, doUpdate bool, isShallow bool) {
	remoteURL := remote.URL()
	local := LocalRepositoryFromURL(remoteURL)

	path := local.FullPath
	newPath := false

	_, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			newPath = true
			err = nil
		}
		utils.PanicIf(err)
	}

	if newPath {
		utils.Log("clone", fmt.Sprintf("%s -> %s", remoteURL, path))

		vcs := remote.VCS()
		if vcs == nil {
			utils.Log("error", fmt.Sprintf("Could not find version control system: %s", remoteURL))
			os.Exit(1)
		}

		vcs.Clone(remoteURL, path, isShallow)
	} else {
		if doUpdate {
			utils.Log("update", path)
			local.VCS().Update(path)
		} else {
			utils.Log("exists", path)
		}
	}
}

func doList(c *cli.Context) {
	query := c.Args().First()
	exact := c.Bool("exact")
	printFullPaths := c.Bool("full-path")
	printUniquePaths := c.Bool("unique")

	var filterFn func(*LocalRepository) bool
	if query == "" {
		filterFn = func(_ *LocalRepository) bool {
			return true
		}
	} else if exact {
		filterFn = func(repo *LocalRepository) bool {
			return repo.Matches(query)
		}
	} else {
		filterFn = func(repo *LocalRepository) bool {
			return strings.Contains(repo.NonHostPath(), query)
		}
	}

	repos := []*LocalRepository{}

	walkLocalRepositories(func(repo *LocalRepository) {
		if filterFn(repo) == false {
			return
		}

		repos = append(repos, repo)
	})

	if printUniquePaths {
		subpathCount := map[string]int{} // Count duplicated subpaths (ex. foo/dotfiles and bar/dotfiles)
		reposCount := map[string]int{}   // Check duplicated repositories among roots

		// Primary first
		for _, repo := range repos {
			if reposCount[repo.RelPath] == 0 {
				for _, p := range repo.Subpaths() {
					subpathCount[p] = subpathCount[p] + 1
				}
			}

			reposCount[repo.RelPath] = reposCount[repo.RelPath] + 1
		}

		for _, repo := range repos {
			if reposCount[repo.RelPath] > 1 && repo.IsUnderPrimaryRoot() == false {
				continue
			}

			for _, p := range repo.Subpaths() {
				if subpathCount[p] == 1 {
					fmt.Println(p)
					break
				}
			}
		}
	} else {
		for _, repo := range repos {
			if printFullPaths {
				fmt.Println(repo.FullPath)
			} else {
				fmt.Println(repo.RelPath)
			}
		}
	}
}

func doLook(c *cli.Context) {
	name := c.Args().First()

	if name == "" {
		cli.ShowCommandHelp(c, "look")
		os.Exit(1)
	}

	reposFound := []*LocalRepository{}
	walkLocalRepositories(func(repo *LocalRepository) {
		if repo.Matches(name) {
			reposFound = append(reposFound, repo)
		}
	})

	switch len(reposFound) {
	case 0:
		utils.Log("error", "No repository found")

	case 1:
		if runtime.GOOS == "windows" {
			cmd := exec.Command(os.Getenv("COMSPEC"))
			cmd.Stdin = os.Stdin
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			cmd.Dir = reposFound[0].FullPath
			err := cmd.Start()
			if err == nil {
				cmd.Wait()
				os.Exit(0)
			}
		} else {
			shell := os.Getenv("SHELL")
			if shell == "" {
				shell = "/bin/sh"
			}

			utils.Log("cd", reposFound[0].FullPath)
			err := os.Chdir(reposFound[0].FullPath)
			utils.PanicIf(err)

			syscall.Exec(shell, []string{shell}, syscall.Environ())
		}

	default:
		utils.Log("error", "More than one repositories are found; Try more precise name")
		for _, repo := range reposFound {
			utils.Log("error", "- "+strings.Join(repo.PathParts, "/"))
		}
	}
}

func doImportStarred(c *cli.Context) {
	user := c.Args().First()
	doUpdate := c.Bool("update")
	isSSH := c.Bool("p")
	isShallow := c.Bool("shallow")

	if user == "" {
		cli.ShowCommandHelp(c, "starred")
		os.Exit(1)
	}

	githubToken := os.Getenv("GHQ_GITHUB_TOKEN")

	if githubToken == "" {
		var err error
		githubToken, err = GitConfigSingle("ghq.github.token")
		utils.PanicIf(err)
	}

	var client *github.Client

	if githubToken != "" {
		oauthTransport := &oauth.Transport{
			Token: &oauth.Token{AccessToken: githubToken},
		}
		client = github.NewClient(oauthTransport.Client())
	} else {
		client = github.NewClient(nil)
	}

	options := &github.ActivityListStarredOptions{Sort: "created"}

	for page := 1; ; page++ {
		options.Page = page

		repositories, res, err := client.Activity.ListStarred(user, options)
		utils.DieIf(err)

		utils.Log("page", fmt.Sprintf("%d/%d", page, res.LastPage))
		for _, repo := range repositories {
			url, err := url.Parse(*repo.HTMLURL)
			if err != nil {
				utils.Log("error", fmt.Sprintf("Could not parse URL <%s>: %s", repo.HTMLURL, err))
				continue
			}
			if isSSH {
				url, err = ConvertGitURLHTTPToSSH(url)
				if err != nil {
					utils.Log("error", fmt.Sprintf("Could not convert URL <%s>: %s", repo.HTMLURL, err))
					continue
				}
			}

			remote, err := NewRemoteRepository(url)
			if utils.ErrorIf(err) {
				continue
			}

			if remote.IsValid() == false {
				utils.Log("skip", fmt.Sprintf("Not a valid repository: %s", url))
				continue
			}

			getRemoteRepository(remote, doUpdate, isShallow)
		}

		if page >= res.LastPage {
			break
		}
	}
}

func doImportPocket(c *cli.Context) {
	doUpdate := c.Bool("update")
	isShallow := c.Bool("shallow")

	if pocket.ConsumerKey == "" {
		utils.Log("error", "Built without consumer key set")
		return
	}

	accessToken, err := GitConfigSingle("ghq.pocket.token")
	utils.PanicIf(err)

	if accessToken == "" {
		receiverURL, ch, err := pocket.StartAccessTokenReceiver()
		utils.PanicIf(err)

		utils.Log("pocket", "Waiting for Pocket authentication callback at "+receiverURL)

		utils.Log("pocket", "Obtaining request token")
		authRequest, err := pocket.ObtainRequestToken(receiverURL)
		utils.DieIf(err)

		url := pocket.GenerateAuthorizationURL(authRequest.Code, receiverURL)
		utils.Log("open", url)

		<-ch

		utils.Log("pocket", "Obtaining access token")
		authorized, err := pocket.ObtainAccessToken(authRequest.Code)
		utils.DieIf(err)

		utils.Log("authorized", authorized.Username)

		accessToken = authorized.AccessToken
		utils.Run("git", "config", "ghq.pocket.token", authorized.AccessToken)
	}

	utils.Log("pocket", "Retrieving github.com entries")
	res, err := pocket.RetrieveGitHubEntries(accessToken)
	utils.DieIf(err)

	for _, item := range res.List {
		url, err := url.Parse(item.ResolvedURL)
		if err != nil {
			utils.Log("error", fmt.Sprintf("Could not parse URL <%s>: %s", item.ResolvedURL, err))
			continue
		}

		remote, err := NewRemoteRepository(url)
		if utils.ErrorIf(err) {
			continue
		}

		if remote.IsValid() == false {
			utils.Log("skip", fmt.Sprintf("Not a valid repository: %s", url))
			continue
		}

		getRemoteRepository(remote, doUpdate, isShallow)
	}
}
<commit_msg>Fix for latest github.com/codegangsta/cli<commit_after>package main

import (
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"syscall"

	"code.google.com/p/goauth2/oauth"
	"github.com/codegangsta/cli"
	"github.com/google/go-github/github"
	"github.com/motemen/ghq/pocket"
	"github.com/motemen/ghq/utils"
)

var Commands = []cli.Command{
	commandGet,
	commandList,
	commandLook,
	commandImport,
}

var commandGet = cli.Command{
	Name:  "get",
	Usage: "Clone/sync with a remote repository",
	Description: `
    Clone a GitHub repository under ghq root direcotry. If the repository is
    already cloned to local, nothing will happen unless '-u' ('--update')
    flag is supplied, in which case 'git remote update' is executed.
    When you use '-p' option, the repository is cloned via SSH.
`,
	Action: doGet,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "update, u", Usage: "Update local repository if cloned already"},
		cli.BoolFlag{Name: "p", Usage: "Clone with SSH"},
		cli.BoolFlag{Name: "shallow", Usage: "Do a shallow clone"},
	},
}

var commandList = cli.Command{
	Name:  "list",
	Usage: "List local repositories",
	Description: `
    List locally cloned repositories. If a query argument is given, only
    repositories whose names contain that query text are listed. '-e'
    ('--exact') forces the match to be an exact one (i.e. the query equals to
    _project_ or _user_/_project_) If '-p' ('--full-path') is given, the full paths
    to the repository root are printed instead of relative ones.
`,
	Action: doList,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "exact, e", Usage: "Perform an exact match"},
		cli.BoolFlag{Name: "full-path, p", Usage: "Print full paths"},
		cli.BoolFlag{Name: "unique", Usage: "Print unique subpaths"},
	},
}

var commandLook = cli.Command{
	Name:  "look",
	Usage: "Look into a local repository",
	Description: `
    Look into a locally cloned repository with the shell.
`,
	Action: doLook,
}

var commandImport = cli.Command{
	Name:  "import",
	Usage: "Import repositories from other web services",
	Subcommands: []cli.Command{
		commandImportStarred,
		commandImportPocket,
	},
}

var commandImportStarred = cli.Command{
	Name:  "starred",
	Usage: "Get all starred GitHub repositories",
	Description: `
    Retrieves GitHub repositories that are starred by the user specified and
    performs 'get' for each of them.
`,
	Action: doImportStarred,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "update, u", Usage: "Update local repository if cloned already"},
		cli.BoolFlag{Name: "p", Usage: "Clone with SSH"},
		cli.BoolFlag{Name: "shallow", Usage: "Do a shallow clone"},
	},
}

var commandImportPocket = cli.Command{
	Name:  "pocket",
	Usage: "Get all github.com entries in Pocket",
	Description: `
    Retrieves Pocket <http://getpocket.com/> entries of github.com and
    performs 'get' for each of them.
`,
	Action: doImportPocket,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "update, u", Usage: "Update local repository if cloned already"},
	},
}

type commandDoc struct {
	Parent    string
	Arguments string
}

var commandDocs = map[string]commandDoc{
	"get":     {"", "[-u] <repository URL> | [-u] [-p] <user>/<project>"},
	"list":    {"", "[-p] [-e] [<query>]"},
	"look":    {"", "<project> | <user>/<project> | <host>/<user>/<project>"},
	"import":  {"", "[-u] [-p] starred <user> | [-u] pocket"},
	"starred": {"import", "[-u] [-p] <user>"},
	"pocket":  {"import", "[-u]"},
}

// Makes template conditionals to generate per-command documents.
func mkCommandsTemplate(genTemplate func(commandDoc) string) string {
	template := "{{if false}}"
	for _, command := range append(Commands, commandImportStarred, commandImportPocket) {
		template = template + fmt.Sprintf("{{else if (eq .Name %q)}}%s", command.Name, genTemplate(commandDocs[command.Name]))
	}
	return template + "{{end}}"
}

func init() {
	argsTemplate := mkCommandsTemplate(func(doc commandDoc) string { return doc.Arguments })
	parentTemplate := mkCommandsTemplate(func(doc commandDoc) string { return string(strings.TrimLeft(doc.Parent+" ", " ")) })

	cli.CommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}

USAGE:
    ghq ` + parentTemplate + `{{.Name}} ` + argsTemplate + `
{{if (len .Description)}}
DESCRIPTION: {{.Description}}
{{end}}{{if (len .Flags)}}
OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}
{{end}}`
}

func doGet(c *cli.Context) {
	argURL := c.Args().Get(0)
	doUpdate := c.Bool("update")
	isShallow := c.Bool("shallow")

	if argURL == "" {
		cli.ShowCommandHelp(c, "get")
		os.Exit(1)
	}

	url, err := NewURL(argURL)
	utils.DieIf(err)

	isSSH := c.Bool("p")
	if isSSH {
		// Assume Git repository if `-p` is given.
		url, err = ConvertGitURLHTTPToSSH(url)
		utils.DieIf(err)
	}

	remote, err := NewRemoteRepository(url)
	utils.DieIf(err)

	if remote.IsValid() == false {
		utils.Log("error", fmt.Sprintf("Not a valid repository: %s", url))
		os.Exit(1)
	}

	getRemoteRepository(remote, doUpdate, isShallow)
}

// getRemoteRepository clones or updates a remote repository remote.
// If doUpdate is true, updates the locally cloned repository. Otherwise does nothing.
// If isShallow is true, does shallow cloning. (no effect if already cloned or the VCS is Mercurial)
func getRemoteRepository(remote RemoteRepository, doUpdate bool, isShallow bool) {
	remoteURL := remote.URL()
	local := LocalRepositoryFromURL(remoteURL)

	path := local.FullPath
	newPath := false

	_, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			newPath = true
			err = nil
		}
		utils.PanicIf(err)
	}

	if newPath {
		utils.Log("clone", fmt.Sprintf("%s -> %s", remoteURL, path))

		vcs := remote.VCS()
		if vcs == nil {
			utils.Log("error", fmt.Sprintf("Could not find version control system: %s", remoteURL))
			os.Exit(1)
		}

		vcs.Clone(remoteURL, path, isShallow)
	} else {
		if doUpdate {
			utils.Log("update", path)
			local.VCS().Update(path)
		} else {
			utils.Log("exists", path)
		}
	}
}

func doList(c *cli.Context) {
	query := c.Args().First()
	exact := c.Bool("exact")
	printFullPaths := c.Bool("full-path")
	printUniquePaths := c.Bool("unique")

	var filterFn func(*LocalRepository) bool
	if query == "" {
		filterFn = func(_ *LocalRepository) bool {
			return true
		}
	} else if exact {
		filterFn = func(repo *LocalRepository) bool {
			return repo.Matches(query)
		}
	} else {
		filterFn = func(repo *LocalRepository) bool {
			return strings.Contains(repo.NonHostPath(), query)
		}
	}

	repos := []*LocalRepository{}

	walkLocalRepositories(func(repo *LocalRepository) {
		if filterFn(repo) == false {
			return
		}

		repos = append(repos, repo)
	})

	if printUniquePaths {
		subpathCount := map[string]int{} // Count duplicated subpaths (ex. foo/dotfiles and bar/dotfiles)
		reposCount := map[string]int{}   // Check duplicated repositories among roots

		// Primary first
		for _, repo := range repos {
			if reposCount[repo.RelPath] == 0 {
				for _, p := range repo.Subpaths() {
					subpathCount[p] = subpathCount[p] + 1
				}
			}

			reposCount[repo.RelPath] = reposCount[repo.RelPath] + 1
		}

		for _, repo := range repos {
			if reposCount[repo.RelPath] > 1 && repo.IsUnderPrimaryRoot() == false {
				continue
			}

			for _, p := range repo.Subpaths() {
				if subpathCount[p] == 1 {
					fmt.Println(p)
					break
				}
			}
		}
	} else {
		for _, repo := range repos {
			if printFullPaths {
				fmt.Println(repo.FullPath)
			} else {
				fmt.Println(repo.RelPath)
			}
		}
	}
}

func doLook(c *cli.Context) {
	name := c.Args().First()

	if name == "" {
		cli.ShowCommandHelp(c, "look")
		os.Exit(1)
	}

	reposFound := []*LocalRepository{}
	walkLocalRepositories(func(repo *LocalRepository) {
		if repo.Matches(name) {
			reposFound = append(reposFound, repo)
		}
	})

	switch len(reposFound) {
	case 0:
		utils.Log("error", "No repository found")

	case 1:
		if runtime.GOOS == "windows" {
			cmd := exec.Command(os.Getenv("COMSPEC"))
			cmd.Stdin = os.Stdin
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			cmd.Dir = reposFound[0].FullPath
			err := cmd.Start()
			if err == nil {
				cmd.Wait()
				os.Exit(0)
			}
		} else {
			shell := os.Getenv("SHELL")
			if shell == "" {
				shell = "/bin/sh"
			}

			utils.Log("cd", reposFound[0].FullPath)
			err := os.Chdir(reposFound[0].FullPath)
			utils.PanicIf(err)

			syscall.Exec(shell, []string{shell}, syscall.Environ())
		}

	default:
		utils.Log("error", "More than one repositories are found; Try more precise name")
		for _, repo := range reposFound {
			utils.Log("error", "- "+strings.Join(repo.PathParts, "/"))
		}
	}
}

func doImportStarred(c *cli.Context) {
	user := c.Args().First()
	doUpdate := c.Bool("update")
	isSSH := c.Bool("p")
	isShallow := c.Bool("shallow")

	if user == "" {
		cli.ShowCommandHelp(c, "starred")
		os.Exit(1)
	}

	githubToken := os.Getenv("GHQ_GITHUB_TOKEN")

	if githubToken == "" {
		var err error
		githubToken, err = GitConfigSingle("ghq.github.token")
		utils.PanicIf(err)
	}

	var client *github.Client

	if githubToken != "" {
		oauthTransport := &oauth.Transport{
			Token: &oauth.Token{AccessToken: githubToken},
		}
		client = github.NewClient(oauthTransport.Client())
	} else {
		client = github.NewClient(nil)
	}

	options := &github.ActivityListStarredOptions{Sort: "created"}

	for page := 1; ; page++ {
		options.Page = page

		repositories, res, err := client.Activity.ListStarred(user, options)
		utils.DieIf(err)

		utils.Log("page", fmt.Sprintf("%d/%d", page, res.LastPage))
		for _, repo := range repositories {
			url, err := url.Parse(*repo.HTMLURL)
			if err != nil {
				utils.Log("error", fmt.Sprintf("Could not parse URL <%s>: %s", repo.HTMLURL, err))
				continue
			}
			if isSSH {
				url, err = ConvertGitURLHTTPToSSH(url)
				if err != nil {
					utils.Log("error", fmt.Sprintf("Could not convert URL <%s>: %s", repo.HTMLURL, err))
					continue
				}
			}

			remote, err := NewRemoteRepository(url)
			if utils.ErrorIf(err) {
				continue
			}

			if remote.IsValid() == false {
				utils.Log("skip", fmt.Sprintf("Not a valid repository: %s", url))
				continue
			}

			getRemoteRepository(remote, doUpdate, isShallow)
		}

		if page >= res.LastPage {
			break
		}
	}
}

func doImportPocket(c *cli.Context) {
	doUpdate := c.Bool("update")
	isShallow := c.Bool("shallow")

	if pocket.ConsumerKey == "" {
		utils.Log("error", "Built without consumer key set")
		return
	}

	accessToken, err := GitConfigSingle("ghq.pocket.token")
	utils.PanicIf(err)

	if accessToken == "" {
		receiverURL, ch, err := pocket.StartAccessTokenReceiver()
		utils.PanicIf(err)

		utils.Log("pocket", "Waiting for Pocket authentication callback at "+receiverURL)

		utils.Log("pocket", "Obtaining request token")
		authRequest, err := pocket.ObtainRequestToken(receiverURL)
		utils.DieIf(err)

		url := pocket.GenerateAuthorizationURL(authRequest.Code, receiverURL)
		utils.Log("open", url)

		<-ch

		utils.Log("pocket", "Obtaining access token")
		authorized, err := pocket.ObtainAccessToken(authRequest.Code)
		utils.DieIf(err)

		utils.Log("authorized", authorized.Username)

		accessToken = authorized.AccessToken
		utils.Run("git", "config", "ghq.pocket.token", authorized.AccessToken)
	}

	utils.Log("pocket", "Retrieving github.com entries")
	res, err := pocket.RetrieveGitHubEntries(accessToken)
	utils.DieIf(err)

	for _, item := range res.List {
		url, err := url.Parse(item.ResolvedURL)
		if err != nil {
			utils.Log("error", fmt.Sprintf("Could not parse URL <%s>: %s", item.ResolvedURL, err))
			continue
		}

		remote, err := NewRemoteRepository(url)
		if utils.ErrorIf(err) {
			continue
		}

		if remote.IsValid() == false {
			utils.Log("skip", fmt.Sprintf("Not a valid repository: %s", url))
			continue
		}

		getRemoteRepository(remote, doUpdate, isShallow)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/bwmarrin/discordgo"
	"io"
	"os"
	"os/exec"
)

// generic command struct which contains name, description, and a function
type command struct {
	name             string                                                                                // human-readable name of the command
	description      string                                                                                // description of command's function
	usage            string                                                                                // example of how to correctly use command - [] for optional arguments, <> for required arguments
	verbs            []string                                                                                // all verbs which are mapped to the same command
	requiresDatabase bool                                                                                // does this command require database access?
	function         func([]string, *discordgo.Channel, *discordgo.MessageCreate, *discordgo.Session) *commandOutput // function which receives a slice of arguments and returns a string to display to the user
}

// output returned by all command functions, can contain a file to be uploaded
type commandOutput struct {
	response string
	file     io.Reader
	embed    *discordgo.MessageEmbed
}

func initCommands() map[string]*command {
	commandList := []*command{}

	commandList = append(commandList,

		// Define all commands here in the order they will be displayed by the help command
		// The 'usage' field should use the default verb
		// Do not include the command prefix

		&command{
			name:             "Display help",
			description:      "Lists all commands and their purposes.\nCan also display detailed info about a given command.",
			usage:            "help [verb]",
			verbs:            []string{"help", "commands"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				DebugPrint("Running help command.")

				if len(args) <= 0 {

					DebugPrint("No arguments; listing commands.")

					embed := NewEmbed().
						SetTitle("Source").
						SetAuthor("Sunbot " + version).
						//SetDescription("Database enabled: " + strconv.FormatBool(redisEnabled)).
						SetURL("https://github.com/techniponi/sunbot").
						SetImage(discordSession.State.User.AvatarURL("128"))

					for _, cmd := range commandList {
						/*
						if cmd.requiresDatabase && !redisEnabled {
							// Database is not enabled, this command needs it
						} else {
						*/
							embed.AddField(cmd.name, "`"+cfg.DefaultPrefix+cmd.usage+"`")
						//}
					}

					return &commandOutput{embed: embed.MessageEmbed}
				}

				DebugPrint("Verb was given...")

				// check if command exists
				if cmd, ok := commands[args[0]]; ok {

					embed := NewEmbed().
						SetTitle(cmd.name).
						SetDescription(cmd.description).
						AddField("Usage", "`"+cfg.DefaultPrefix+cmd.usage+"`")

					DebugPrint("Providing help for given verb.")

					// compile verbs
					verbOutput := ""
					for index, verb := range cmd.verbs {
						// don't add a comma if it's the last one
						if index == (len(cmd.verbs) - 1) {
							verbOutput += "`" + cfg.DefaultPrefix + verb + "`"
						} else {
							verbOutput += "`" + cfg.DefaultPrefix + verb + "`, "
						}
					}
					embed.AddField("Verbs", verbOutput)
					return &commandOutput{embed: embed.MessageEmbed}
				}
				DebugPrint("Given verb was not found.")
				return &commandOutput{response: "That isn't a valid command."}
			},
		},

		&command{
			name:             "Derpibooru search",
			description:      "Searches Derpibooru with the given tags as the query, chooses a random result to display.\nUse commas to separate tags like you would on the website.",
			usage:            "derpi <tags>",
			verbs:            []string{"derpi", "db", "derpibooru"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {
				if len(args) < 1 {
					DebugPrint("User ran derpibooru command with no tags given.")
					return &commandOutput{response: "Error: no tags specified"}
				}
				DebugPrint("User is running derpibooru command...")

				searchQuery := ""

				for _, arg := range args {
					searchQuery += arg + " "
				}

				// enforce 'safe' tag if channel is not nsfw
				if !channel.NSFW {
					DebugPrint("Channel #" + channel.Name + " is SFW, adding safe tag...")
					searchQuery += ",safe"
				}

				DebugPrint("Searching with tags:\n" + searchQuery)

				// use derpibooru.go to perform search
				results, err := DerpiSearchWithTags(searchQuery, cfg.DerpiApiKey)
				if err != nil {
					fmt.Println(err)
					return &commandOutput{response: "Error: " + err.Error()}
				}

				// check for results
				if len(results.Search) <= 0 {
					DebugPrint("Derpibooru returned no results.")
					return &commandOutput{response: "Error: no results."}
				}
				DebugPrint("Derpibooru returned results; parsed successfully.")
				// pick one randomly
				output := "http:" + results.Search[RandomRange(0, len(results.Search))].Image

				return &commandOutput{response: output}
			},
		},

		&command{
			name: "Exec",
			description: "Execute a shell command on my server.",
			usage: "exec <command>",
			verbs: []string{"exec"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				hasPermission := false

				// get user object
				DebugPrint("Getting user object")
				user, err := discordSession.State.Member(channel.GuildID, msgEvent.Author.ID)
				if err != nil {
					fmt.Println(err)
					return &commandOutput{response: "Error getting user from exec command"}
				}

				// get roles from that user
				DebugPrint("Getting user's roles")
				for _, roleID := range user.Roles {
					role, err := discordSession.State.Role(channel.GuildID, roleID)
					if err != nil {
						fmt.Println(err)
						return &commandOutput{response: "Error getting roles from user"}
					}

					DebugPrint("Checking for admin permission")
					if role.Permissions&discordgo.PermissionAdministrator != 0 {
						hasPermission = true
					}
				}

				if hasPermission {

					// convert slice to single string
					fullCommand := ""
					for _, arg := range args {
						fullCommand += arg + " "
					}

					cmd := exec.Command("/bin/bash", "-c", fullCommand)
					stdout, err := cmd.Output()

					if err != nil {
						fmt.Println(err)
						return &commandOutput{response: "Error running command"}
					}

					return &commandOutput{
						response: "```sh\n" + string(stdout) + "\n```",
					}
				}else{
					return &commandOutput{response: "Sorry, but only administrators can use that command."}
				}
			},
		},

		&command{
			name:             "Join",
			description:      "I will join the voice channel of the sender.",
			usage:            "join",
			verbs:            []string{"join"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				voiceConnection, err := JoinUserVoiceChannel(discordSession, msgEvent.Author.ID)
				if err != nil {
					fmt.Println(err)
					return &commandOutput{response: "Error joining voice channel - are you in one in this server?"}
				}
				fmt.Println("Joined channel:")
				fmt.Println(voiceConnection)

				// TODO: fix audio
/*
				DebugPrint("Setting options")
				options := dca.StdEncodeOptions
				options.RawOutput = true
				options.Bitrate = 96
				options.Application = "lowdelay"

				DebugPrint("Getting info")
				videoInfo, err := ytdl.GetVideoInfo("https://www.youtube.com/watch?v=gWBZJkfzhNY")
				if err != nil {
					DebugPrint("Error getting info")
					fmt.Println(err)
				}

				DebugPrint("Setting format")
				format := videoInfo.Formats.Extremes(ytdl.FormatAudioBitrateKey, true)[0]
				downloadURL, err := videoInfo.GetDownloadURL(format)
				if err != nil {
					DebugPrint("Error setting format")
					fmt.Println(err)
				}

				DebugPrint("Encoding file")
				encodingSession, err := dca.EncodeFile(downloadURL.String(), options)
				if err != nil {
					DebugPrint("Error encoding file")
					fmt.Println(err)
				}
				defer encodingSession.Cleanup()

				DebugPrint("Initiating stream")
				done := make(chan error)
				dca.NewStream(encodingSession, voiceConnection, done)
				playErr := <- done
				if playErr != nil && playErr != io.EOF {
					fmt.Println(playErr)
				}
*/
				return &commandOutput{response: "Joining your channel!"}
			},
		},

		&command{
			name:             "Leave",
			description:      "I will leave the voice channel.",
			usage:            "leave",
			verbs:            []string{"leave", "disconnect", "quit", "exit"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				for _, voiceChannel := range discordSession.VoiceConnections {
					DebugPrint("Looking for voice channel in this guild...")
					if voiceChannel.GuildID == channel.GuildID {
						DebugPrint("Found a channel in this guild!")
						voiceChannel.Disconnect()
						return &commandOutput{response: "Bye!"}
					}
				}
				return &commandOutput{response: ""}
			},
		},

		&command{
			name:             "Gay",
			description:      "Posts a very gay image.",
			usage:            "gay",
			verbs:            []string{"gay"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {
				file, err := os.Open("img/gaybats.png") // TODO: move this to database; allow users to add images (permission system?)
				if err != nil {
					fmt.Println(err)
					return &commandOutput{response: "Error opening file"}

				}
				return &commandOutput{file: file}
			},
		},

	/*
		&command{
			name:             "User stats",
			description:      "Displays the statistics of the user.",
			usage:            "stats [user]", // TODO: implement pinging users
			verbs:            []string{"stats"},
			requiresDatabase: true,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				if len(args) > 0 {
					if len(msgEvent.Mentions) > 0 {
						// User tagged someone else
						taggedUser := msgEvent.Mentions[0] // only the first one

						userDb, err := GetUser(taggedUser, false)
						if err != nil {
							return &commandOutput{response: "That user doesn't exist in the database yet. They need to chat some!"}
						}

						posts := userDb.Val()["posts"]
						return &commandOutput{response: taggedUser.Username + " has made " + posts + " posts!"} // TODO: format as embed, show more values
					}
					// user didn't tag anyone
					// TODO: accept aliases as well as mentions
					return &commandOutput{response: "To see someone's stats, tag the person directly!"}
				}
				// User's own stats
				userDb, err := GetUser(msgEvent.Author, false)
				if err != nil {
					return &commandOutput{response: "You don't exist in the database yet. You need to chat some!"}
				}
				posts := userDb.Val()["posts"]
				return &commandOutput{response: "You have made " + posts + " posts!"} // TODO: format as embed, show more values
			},
		},
	*/
	)

	// Map for matching verbs to commands
	commandMap := make(map[string]*command)

	// Loop through commandList to get each verb
	for _, cmd := range commandList {
		for _, verb := range cmd.verbs {
			commandMap[verb] = cmd
			DebugPrint("Mapped '" + verb + "' to '" + cmd.name + "'")
		}
	}

	return commandMap
}
<commit_msg>clarity<commit_after>package main

import (
	"fmt"
	"github.com/bwmarrin/discordgo"
	"io"
	"os"
	"os/exec"
)

// generic command struct which contains name, description, and a function
type command struct {
	name             string                                                                                // human-readable name of the command
	description      string                                                                                // description of command's function
	usage            string                                                                                // example of how to correctly use command - [] for optional arguments, <> for required arguments
	verbs            []string                                                                                // all verbs which are mapped to the same command
	requiresDatabase bool                                                                                // does this command require database access?
	function         func([]string, *discordgo.Channel, *discordgo.MessageCreate, *discordgo.Session) *commandOutput // function which receives a slice of arguments and returns a string to display to the user
}

// output returned by all command functions, can contain a file to be uploaded
type commandOutput struct {
	response string
	file     io.Reader
	embed    *discordgo.MessageEmbed
}

func initCommands() map[string]*command {
	commandList := []*command{}

	commandList = append(commandList,

		// Define all commands here in the order they will be displayed by the help command
		// The 'usage' field should use the default verb
		// Do not include the command prefix

		&command{
			name:             "Display help",
			description:      "Lists all commands and their purposes.\nCan also display detailed info about a given command.",
			usage:            "help [verb]",
			verbs:            []string{"help", "commands"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				DebugPrint("Running help command.")

				if len(args) <= 0 {

					DebugPrint("No arguments; listing commands.")

					embed := NewEmbed().
						SetTitle("Source").
						SetAuthor("Sunbot " + version).
						//SetDescription("Database enabled: " + strconv.FormatBool(redisEnabled)).
						SetURL("https://github.com/techniponi/sunbot").
						SetImage(discordSession.State.User.AvatarURL("128"))

					for _, cmd := range commandList {
						/*
						if cmd.requiresDatabase && !redisEnabled {
							// Database is not enabled, this command needs it
						} else {
						*/
							embed.AddField(cmd.name, "`"+cfg.DefaultPrefix+cmd.usage+"`")
						//}
					}

					return &commandOutput{embed: embed.MessageEmbed}
				}

				DebugPrint("Verb was given...")

				// check if command exists
				if cmd, ok := commands[args[0]]; ok {

					embed := NewEmbed().
						SetTitle(cmd.name).
						SetDescription(cmd.description).
						AddField("Usage", "`"+cfg.DefaultPrefix+cmd.usage+"`")

					DebugPrint("Providing help for given verb.")

					// compile verbs
					verbOutput := ""
					for index, verb := range cmd.verbs {
						// don't add a comma if it's the last one
						if index == (len(cmd.verbs) - 1) {
							verbOutput += "`" + cfg.DefaultPrefix + verb + "`"
						} else {
							verbOutput += "`" + cfg.DefaultPrefix + verb + "`, "
						}
					}
					embed.AddField("Verbs", verbOutput)
					return &commandOutput{embed: embed.MessageEmbed}
				}
				DebugPrint("Given verb was not found.")
				return &commandOutput{response: "That isn't a valid command."}
			},
		},

		&command{
			name:             "Derpibooru search",
			description:      "Searches Derpibooru with the given tags as the query, chooses a random result to display.\nUse commas to separate tags like you would on the website.",
			usage:            "derpi <tags>",
			verbs:            []string{"derpi", "db", "derpibooru"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {
				if len(args) < 1 {
					DebugPrint("User ran derpibooru command with no tags given.")
					return &commandOutput{response: "Error: no tags specified"}
				}
				DebugPrint("User is running derpibooru command...")

				searchQuery := ""

				for _, arg := range args {
					searchQuery += arg + " "
				}

				// enforce 'safe' tag if channel is not nsfw
				if !channel.NSFW {
					DebugPrint("Channel #" + channel.Name + " is SFW, adding safe tag...")
					searchQuery += ",safe"
				}

				DebugPrint("Searching with tags:\n" + searchQuery)

				// use derpibooru.go to perform search
				results, err := DerpiSearchWithTags(searchQuery, cfg.DerpiApiKey)
				if err != nil {
					fmt.Println(err)
					return &commandOutput{response: "Error: " + err.Error()}
				}

				// check for results
				if len(results.Search) <= 0 {
					DebugPrint("Derpibooru returned no results.")
					return &commandOutput{response: "Error: no results."}
				}
				DebugPrint("Derpibooru returned results; parsed successfully.")
				// pick one randomly
				output := "http:" + results.Search[RandomRange(0, len(results.Search))].Image

				return &commandOutput{response: output}
			},
		},

		&command{
			name: "Exec",
			description: "Execute a shell command on my server.\nRequires admin permissions.",
			usage: "exec <command>",
			verbs: []string{"exec"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				hasPermission := false

				// get user object
				DebugPrint("Getting user object")
				user, err := discordSession.State.Member(channel.GuildID, msgEvent.Author.ID)
				if err != nil {
					fmt.Println(err)
					return &commandOutput{response: "Error getting user from exec command"}
				}

				// get roles from that user
				DebugPrint("Getting user's roles")
				for _, roleID := range user.Roles {
					role, err := discordSession.State.Role(channel.GuildID, roleID)
					if err != nil {
						fmt.Println(err)
						return &commandOutput{response: "Error getting roles from user"}
					}

					DebugPrint("Checking for admin permission")
					if role.Permissions&discordgo.PermissionAdministrator != 0 {
						hasPermission = true
					}
				}

				if hasPermission {

					// convert slice to single string
					fullCommand := ""
					for _, arg := range args {
						fullCommand += arg + " "
					}

					cmd := exec.Command("/bin/bash", "-c", fullCommand)
					stdout, err := cmd.Output()

					if err != nil {
						fmt.Println(err)
						return &commandOutput{response: "Error running command"}
					}

					return &commandOutput{
						response: "```sh\n" + string(stdout) + "\n```",
					}
				}else{
					return &commandOutput{response: "Sorry, but only administrators can use that command."}
				}
			},
		},

		&command{
			name:             "Join",
			description:      "I will join the voice channel of the sender.",
			usage:            "join",
			verbs:            []string{"join"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				voiceConnection, err := JoinUserVoiceChannel(discordSession, msgEvent.Author.ID)
				if err != nil {
					fmt.Println(err)
					return &commandOutput{response: "Error joining voice channel - are you in one in this server?"}
				}
				fmt.Println("Joined channel:")
				fmt.Println(voiceConnection)

				// TODO: fix audio
/*
				DebugPrint("Setting options")
				options := dca.StdEncodeOptions
				options.RawOutput = true
				options.Bitrate = 96
				options.Application = "lowdelay"

				DebugPrint("Getting info")
				videoInfo, err := ytdl.GetVideoInfo("https://www.youtube.com/watch?v=gWBZJkfzhNY")
				if err != nil {
					DebugPrint("Error getting info")
					fmt.Println(err)
				}

				DebugPrint("Setting format")
				format := videoInfo.Formats.Extremes(ytdl.FormatAudioBitrateKey, true)[0]
				downloadURL, err := videoInfo.GetDownloadURL(format)
				if err != nil {
					DebugPrint("Error setting format")
					fmt.Println(err)
				}

				DebugPrint("Encoding file")
				encodingSession, err := dca.EncodeFile(downloadURL.String(), options)
				if err != nil {
					DebugPrint("Error encoding file")
					fmt.Println(err)
				}
				defer encodingSession.Cleanup()

				DebugPrint("Initiating stream")
				done := make(chan error)
				dca.NewStream(encodingSession, voiceConnection, done)
				playErr := <- done
				if playErr != nil && playErr != io.EOF {
					fmt.Println(playErr)
				}
*/
				return &commandOutput{response: "Joining your channel!"}
			},
		},

		&command{
			name:             "Leave",
			description:      "I will leave the voice channel.",
			usage:            "leave",
			verbs:            []string{"leave", "disconnect", "quit", "exit"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				for _, voiceChannel := range discordSession.VoiceConnections {
					DebugPrint("Looking for voice channel in this guild...")
					if voiceChannel.GuildID == channel.GuildID {
						DebugPrint("Found a channel in this guild!")
						voiceChannel.Disconnect()
						return &commandOutput{response: "Bye!"}
					}
				}
				return &commandOutput{response: ""}
			},
		},

		&command{
			name:             "Gay",
			description:      "Posts a very gay image.",
			usage:            "gay",
			verbs:            []string{"gay"},
			requiresDatabase: false,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {
				file, err := os.Open("img/gaybats.png") // TODO: move this to database; allow users to add images (permission system?)
				if err != nil {
					fmt.Println(err)
					return &commandOutput{response: "Error opening file"}

				}
				return &commandOutput{file: file}
			},
		},

	/*
		&command{
			name:             "User stats",
			description:      "Displays the statistics of the user.",
			usage:            "stats [user]", // TODO: implement pinging users
			verbs:            []string{"stats"},
			requiresDatabase: true,
			function: func(args []string, channel *discordgo.Channel, msgEvent *discordgo.MessageCreate, discordSession *discordgo.Session) *commandOutput {

				if len(args) > 0 {
					if len(msgEvent.Mentions) > 0 {
						// User tagged someone else
						taggedUser := msgEvent.Mentions[0] // only the first one

						userDb, err := GetUser(taggedUser, false)
						if err != nil {
							return &commandOutput{response: "That user doesn't exist in the database yet. They need to chat some!"}
						}

						posts := userDb.Val()["posts"]
						return &commandOutput{response: taggedUser.Username + " has made " + posts + " posts!"} // TODO: format as embed, show more values
					}
					// user didn't tag anyone
					// TODO: accept aliases as well as mentions
					return &commandOutput{response: "To see someone's stats, tag the person directly!"}
				}
				// User's own stats
				userDb, err := GetUser(msgEvent.Author, false)
				if err != nil {
					return &commandOutput{response: "You don't exist in the database yet. You need to chat some!"}
				}
				posts := userDb.Val()["posts"]
				return &commandOutput{response: "You have made " + posts + " posts!"} // TODO: format as embed, show more values
			},
		},
	*/
	)

	// Map for matching verbs to commands
	commandMap := make(map[string]*command)

	// Loop through commandList to get each verb
	for _, cmd := range commandList {
		for _, verb := range cmd.verbs {
			commandMap[verb] = cmd
			DebugPrint("Mapped '" + verb + "' to '" + cmd.name + "'")
		}
	}

	return commandMap
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"
	"time"

	"github.com/codegangsta/cli"
)

const (
	MetaDir = ".gic"
	TemplateDir = "templates"
	DefaultEditor = "vi"
	Permission = 0777
	PersonalAccessTokenKey = "github.token"
)

var Commands = []cli.Command{
	commandInit,
	commandList,
	commandEdit,
	commandPreview,
	commandApply,
}

var commandInit = cli.Command{
	Name:  "init",
	Usage: "Initialize gic settings of project.",
	Action: doInit,
}

var commandList = cli.Command{
	Name:  "list",
	Usage: "Display a list of templates.",
	Action: doList,
}

var commandEdit = cli.Command{
	Name:  "edit",
	Usage: "Edit template.",
	Action: doEdit,
}

var commandPreview = cli.Command{
	Name:  "preview",
	Usage: "Display a preview of template.",
	Action: doPreview,
}

var commandApply = cli.Command{
	Name:  "apply",
	Usage: "Create Issue with given template.",
	Action: doApply,
}

func doInit(c *cli.Context) {
	if requireInitialize() {
		template_dir := getTemplateDir()
		os.MkdirAll(template_dir, Permission)
		fmt.Printf("Created %s\n", template_dir)
	}
}

func doList(c *cli.Context) {
	exitIfNotInitialized()

	for _, template := range getTemplates() {
		fmt.Println(getTemplateName(template))
	}
}

func doEdit(c *cli.Context) {
	exitIfNotInitialized()
	exitIfNotSpecifiedTemplate(len(c.Args()))

	editor := getEditor()
	template_path := getTemplatePath(c.Args().First())
	cmd := exec.Command(editor, template_path)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	err := cmd.Run()

	if err != nil {
		fmt.Println(err)
	}
}

func doPreview(c *cli.Context) {
	exitIfNotInitialized()
	exitIfNotSpecifiedTemplate(len(c.Args()))

	tmpl := template.Must(template.ParseFiles(getTemplatePath(c.Args().First())))
	helper := newHelper()
	err := tmpl.Execute(os.Stdout, *helper)

	if err != nil {
		fail("Render template fails.")
	}
}

func doApply(c *cli.Context) {
	exitIfNotInitialized()
	exitIfNotSpecifiedTemplate(len(c.Args()))

	title := createTitle()

	tmpl := template.Must(template.ParseFiles(getTemplatePath(c.Args().First())))
	var body bytes.Buffer
	helper := newHelper()
	err := tmpl.Execute(&body, *helper)
	if err != nil {
		fail("Render template fails")
	}

	owner, repo := parseOriginUrl()

	token, err := getGitConfig(PersonalAccessTokenKey)
	if err != nil {
		fail("Must be token settings to .gitconfig")
	}

	createIssue(title, body.String(), owner, repo, token)
}

func exitIfNotInitialized() {
	if requireInitialize() {
		fail("Require initialize. Please execute `gic init`.")
	}
}

func exitIfNotSpecifiedTemplate(arg_size int) {
	if arg_size < 1 {
		fail("Require template name.")
	}
}

func createTitle() string {
	now := time.Now().Format("20060102150405")
	return "Post from gic " + now
}

func requireInitialize() bool {
	_, err := os.Stat(getTemplateDir())

	if os.IsNotExist(err) {
		return true
	} else {
		return false
	}
}

func getTemplates() (templates []string) {
	templates, err := filepath.Glob(path.Join(getTemplateDir(), "*"))

	if err != nil {
		fail("Get template list fails.")
	}
	return
}

func getTemplateName(template_path string) string {
	return path.Base(template_path)
}

func getTemplatePath(template_name string) string {
	return path.Join(getTemplateDir(), template_name)
}

func getTemplateDir() string {
	return path.Join(getMetaPath(), TemplateDir)
}

func getMetaPath() string {
	out, err := getProjectRoot()

	if err != nil {
		fail(out)
	}
	return path.Join(out, MetaDir)
}

func getProjectRoot() (out string, err error) {
	cmd := exec.Command("git", "rev-parse", "--show-toplevel")
	var result bytes.Buffer
	cmd.Stdout = &result

	err = cmd.Run()
	out = strings.TrimSpace(result.String())
	return
}

func getEditor() (editor string) {
	envs := getEnvMap()
	editor = envs["EDITOR"]

	if len(editor) == 0 {
		editor = DefaultEditor
	}
	return
}

func parseOriginUrl() (owner, repo string) {
	origin_url, err := getGitConfig("remote.origin.url")
	if err != nil {
		fail("Origin URI not found.")
	}

	re := regexp.MustCompile(`^(?:git@github\.com:|https://github\.com/)([^/]+)/([^/]+?)(?:\.git)$`)
	submatch := re.FindSubmatch([]byte(origin_url))
	if len(submatch) != 3 {
		fail("Origin URL parse error.")
	}

	return string(submatch[1]), string(submatch[2])
}

func getGitConfig(key string) (out string, err error) {
	cmd := exec.Command("git", "config", key)
	var result bytes.Buffer
	cmd.Stdout = &result

	err = cmd.Run()
	out = strings.TrimSpace(result.String())
	return
}

func getEnvMap() (envs map[string]string) {
	envs = make(map[string]string)

	for _, env := range os.Environ() {
		key_and_value := strings.SplitN(env, "=", 2)
		envs[key_and_value[0]] = key_and_value[1]
	}
	return
}

func fail(message string) {
	fmt.Println(message)
	os.Exit(1)
}

<commit_msg>Refactor<commit_after>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/cli"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"
	"time"
)

const (
	// MetaDir is saved templates and settings
	MetaDir = ".gic"
	// TemplateDir is saved templates path from MetaDir
	TemplateDir = "templates"
	// DefaultEditor is used when there is no EDITOR environment variable
	DefaultEditor = "vi"
	// Permission of MetaDir
	Permission = 0777
	// PersonalAccessTokenKey in .gitconfig
	PersonalAccessTokenKey = "github.token"
)

// Commands of CLI
var Commands = []cli.Command{
	commandInit,
	commandList,
	commandEdit,
	commandPreview,
	commandApply,
}

var commandInit = cli.Command{
	Name:   "init",
	Usage:  "Initialize gic settings of project.",
	Action: doInit,
}

var commandList = cli.Command{
	Name:   "list",
	Usage:  "Display a list of templates.",
	Action: doList,
}

var commandEdit = cli.Command{
	Name:   "edit",
	Usage:  "Edit template.",
	Action: doEdit,
}

var commandPreview = cli.Command{
	Name:   "preview",
	Usage:  "Display a preview of template.",
	Action: doPreview,
}

var commandApply = cli.Command{
	Name:   "apply",
	Usage:  "Create Issue with given template.",
	Action: doApply,
}

func doInit(c *cli.Context) {
	if requireInitialize() {
		templateDir := getTemplateDir()
		os.MkdirAll(templateDir, Permission)
		fmt.Printf("Created %s\n", templateDir)
	}
}

func doList(c *cli.Context) {
	exitIfNotInitialized()

	for _, template := range getTemplates() {
		fmt.Println(getTemplateName(template))
	}
}

func doEdit(c *cli.Context) {
	exitIfNotInitialized()
	exitIfNotSpecifiedTemplate(len(c.Args()))

	editor := getEditor()
	templatePath := getTemplatePath(c.Args().First())
	cmd := exec.Command(editor, templatePath)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	err := cmd.Run()

	if err != nil {
		fmt.Println(err)
	}
}

func doPreview(c *cli.Context) {
	exitIfNotInitialized()
	exitIfNotSpecifiedTemplate(len(c.Args()))

	tmpl := template.Must(template.ParseFiles(getTemplatePath(c.Args().First())))
	helper := newHelper()
	err := tmpl.Execute(os.Stdout, *helper)

	if err != nil {
		fail("Render template fails.")
	}
}

func doApply(c *cli.Context) {
	exitIfNotInitialized()
	exitIfNotSpecifiedTemplate(len(c.Args()))

	title := createTitle()

	tmpl := template.Must(template.ParseFiles(getTemplatePath(c.Args().First())))
	var body bytes.Buffer
	helper := newHelper()
	err := tmpl.Execute(&body, *helper)
	if err != nil {
		fail("Render template fails")
	}

	owner, repo := parseOriginURL()

	token, err := getGitConfig(PersonalAccessTokenKey)
	if err != nil {
		fail("Must be token settings to .gitconfig")
	}

	createIssue(title, body.String(), owner, repo, token)
}

func exitIfNotInitialized() {
	if requireInitialize() {
		fail("Require initialize. Please execute `gic init`.")
	}
}

func exitIfNotSpecifiedTemplate(argSize int) {
	if argSize < 1 {
		fail("Require template name.")
	}
}

func createTitle() string {
	now := time.Now().Format("20060102150405")
	return "Post from gic " + now
}

func requireInitialize() bool {
	_, err := os.Stat(getTemplateDir())

	if os.IsNotExist(err) {
		return true
	}
	return false
}

func getTemplates() (templates []string) {
	templates, err := filepath.Glob(path.Join(getTemplateDir(), "*"))

	if err != nil {
		fail("Get template list fails.")
	}
	return
}

func getTemplateName(templatePath string) string {
	return path.Base(templatePath)
}

func getTemplatePath(templateName string) string {
	return path.Join(getTemplateDir(), templateName)
}

func getTemplateDir() string {
	return path.Join(getMetaPath(), TemplateDir)
}

func getMetaPath() string {
	out, err := getProjectRoot()

	if err != nil {
		fail(out)
	}
	return path.Join(out, MetaDir)
}

func getProjectRoot() (out string, err error) {
	cmd := exec.Command("git", "rev-parse", "--show-toplevel")
	var result bytes.Buffer
	cmd.Stdout = &result

	err = cmd.Run()
	out = strings.TrimSpace(result.String())
	return
}

func getEditor() (editor string) {
	envs := getEnvMap()
	editor = envs["EDITOR"]

	if len(editor) == 0 {
		editor = DefaultEditor
	}
	return
}

func parseOriginURL() (owner, repo string) {
	originURL, err := getGitConfig("remote.origin.url")
	if err != nil {
		fail("Origin URI not found.")
	}

	re := regexp.MustCompile(`^(?:git@github\.com:|https://github\.com/)([^/]+)/([^/]+?)(?:\.git)$`)
	submatch := re.FindSubmatch([]byte(originURL))
	if len(submatch) != 3 {
		fail("Origin URL parse error.")
	}

	return string(submatch[1]), string(submatch[2])
}

func getGitConfig(key string) (out string, err error) {
	cmd := exec.Command("git", "config", key)
	var result bytes.Buffer
	cmd.Stdout = &result

	err = cmd.Run()
	out = strings.TrimSpace(result.String())
	return
}

func getEnvMap() (envs map[string]string) {
	envs = make(map[string]string)

	for _, env := range os.Environ() {
		keyAndValue := strings.SplitN(env, "=", 2)
		envs[key_and_value[0]] = keyAndValue[1]
	}
	return
}

func fail(message string) {
	fmt.Println(message)
	os.Exit(1)
}
<|endoftext|>


<commit_before>package main

import (
	"math/rand"
	"runtime"
	"strings"
	"time"
)

var Commands = map[string]func(string) string{
	"about": about,
	"pick":  pick,
	"echo":  echo,
}

func about(_ string) string {
	return "BlizzyBotGo: Written in Go " + runtime.Version()
}

func echo(arg string) string {
	return arg
}

func pick(choices string) string {
	options := strings.Split(choices, ",")
	rand.Seed(time.Now().Unix())
	choice := rand.Intn(len(choices))
	return "I randomly pick \"" + string(options[choice]) + "\""
}
<commit_msg>Remove echo command.<commit_after>package main

import (
	"math/rand"
	"runtime"
	"strings"
	"time"
)

// TODO: More commands.

var Commands = map[string]func(string) string{
	"about": about,
	"pick":  pick,
}

func about(_ string) string {
	return "BlizzyBotGo: Written in Go " + runtime.Version()
}

func pick(choices string) string {
	options := strings.Split(choices, ",")
	rand.Seed(time.Now().Unix())
	choice := rand.Intn(len(choices))
	return "I randomly pick \"" + string(options[choice]) + "\""
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"net/http"
)

func index(w http.ResponseWriter, r *http.Request) {
	var output string
	output = "MongoDumpServer v0.1"

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(output); err != nil {
		log.Println("Failed", err)
	}
}

func dumpCreate(w http.ResponseWriter, r *http.Request) {
	var target dumpTarget

	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
	if err != nil {
		log.Println("Failed", err)
	}
	if err := r.Body.Close(); err != nil {
		log.Println("Failed", err)
	}
	if err := json.Unmarshal(body, &target); err != nil {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.WriteHeader(422) //unprocessable entity
		if err := json.NewEncoder(w).Encode(err); err != nil {
			log.Println("Failed", err)
		}
	}

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusCreated)
	if err := json.NewEncoder(w).Encode("Backup started"); err != nil {
		log.Println("Failed to encode json", err)
	}

	go dumpStart(target)
}
<commit_msg>fix success message<commit_after>package main

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"net/http"
)

func index(w http.ResponseWriter, r *http.Request) {
	var output string
	output = "MongoDumpServer v0.1"

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(output); err != nil {
		log.Println("Failed", err)
	}
}

func dumpCreate(w http.ResponseWriter, r *http.Request) {
	var target dumpTarget

	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
	if err != nil {
		log.Println("Failed", err)
	}
	if err := r.Body.Close(); err != nil {
		log.Println("Failed", err)
	}
	if err := json.Unmarshal(body, &target); err != nil {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.WriteHeader(422) //unprocessable entity
		if err := json.NewEncoder(w).Encode(err); err != nil {
			log.Println("Failed", err)
		}
	}

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusCreated)
	if err := json.NewEncoder(w).Encode("Backup started successfully"); err != nil {
		log.Println("Failed to encode json", err)
	}

	go dumpStart(target)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"strings"
)

func upload(url string, data map[string]string,
	paramname string, filename string,
) error {
	client := &http.Client{}
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	fileWriter, err := bodyWriter.CreateFormFile(paramname, filename)
	if err != nil {
		fmt.Println("error writing to buffer")
		return err
	}

	f, err := os.Open(filename)
	if err != nil {
		fmt.Println("error open file")
		return err
	}

	_, err = io.Copy(fileWriter, f)
	if err != nil {
		return err
	}

	for k, v := range data {
		bodyWriter.WriteField(k, v)
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	req, err := http.NewRequest("POST", url, bodyBuf)
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("User-Agent", "go-bild/0.1.0")

	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	body := string(respBody)
	fmt.Println(resp.StatusCode)
	// fmt.Println(body)
	urls := strings.Split(body, "\n")
	fmt.Println(urls[0])
	fmt.Println(urls[len(urls)-1])
	return nil

}

func main() {
	data := map[string]string{
		"t":      "1",
		"C1":     "ON",
		"upload": "1",
	}
	url := "http://www.bild.me/index.php"
	upload(url, data, "F1", "up-download.jpg")
}
<commit_msg>parse result and support glob<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

func upload(url string, data map[string]string,
	paramname string, filename string,
) (s []string, err error) {
	client := &http.Client{}
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	fileWriter, err := bodyWriter.CreateFormFile(paramname, filename)
	if err != nil {
		fmt.Println("error writing to buffer")
		return
	}

	f, err := os.Open(filename)
	if err != nil {
		fmt.Println("error open file")
		return
	}

	_, err = io.Copy(fileWriter, f)
	if err != nil {
		return
	}

	for k, v := range data {
		bodyWriter.WriteField(k, v)
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	req, err := http.NewRequest("POST", url, bodyBuf)
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("User-Agent", "go-bild/0.1.0")

	resp, err := client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	body := string(respBody)
	if resp.StatusCode >= 400 {
		return
	}
	// fmt.Println(body)
	urls := strings.Split(body, "\n")
	// fmt.Println(urls[0])
	// fmt.Println(urls[len(urls)-1])
	// fmt.Println("\n")
	s = []string{
		urls[0],
		urls[len(urls)-1],
	}

	return
}

func main() {
	data := map[string]string{
		"t":      "1",
		"C1":     "ON",
		"upload": "1",
	}
	url := "http://www.bild.me/index.php"
	fileSlice := []string{}
	files := os.Args[1:]

	// 支持通配符
	for _, file := range files {
		matches, err := filepath.Glob(file)
		if err == nil {
			fileSlice = append(fileSlice, matches...)
		}
	}
	if len(fileSlice) == 0 {
		fmt.Println("need files: bild FILE [FILE ...]")
		os.Exit(1)
	}
	var wg sync.WaitGroup

	for _, f := range fileSlice {
		wg.Add(1)
		go func(f string) {
			defer wg.Done()
			s, err := upload(url, data, "F1", f)
			if err == nil {
				fmt.Println(f+":", s[1])
			}
		}(f)
	}
	wg.Wait()
}
<|endoftext|>


<commit_before>package blob

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
)

type Blob struct {
	header []indexItem
	data   []byte
}

type indexItem struct {
	id    string
	start uint64
	end   uint64
}

// New creates an empty blob that you can add your resources to and later save
// it to a file.
func New() *Blob {
	return &Blob{}
}

// ItemCount returns the number of blob items. You can use GetByIndex with an
// index from 0 to ItemCount()-1 to retrieve an item at a specific index.
func (b *Blob) ItemCount() int {
	return len(b.header)
}

// Append adds the given data at the end of the blob.
func (b *Blob) Append(id string, data []byte) {
	b.header = append(
		b.header,
		indexItem{
			id,
			uint64(len(b.data)),
			uint64(len(b.data) + len(data)),
		},
	)
	b.data = append(b.data, data...)
}

// GetByID searches the blob for an entry with the given ID and returns the
// first one found (if there are multiple entries with this ID only the first
// one will ever be returned by this function).
// If an entry was found, data contains the binary data and found will be true,
// if no such entry exists, data will be nil and found will be false.
func (b *Blob) GetByID(id string) (data []byte, found bool) {
	for i := range b.header {
		if b.header[i].id == id {
			data = b.data[b.header[i].start:b.header[i].end]
			found = true
			return
		}
	}
	return
}

// GetIDAtIndex returns the ID of the entry at index i or the empty string if
// the given index is out of bounds. See ItemCount for the number of items.
func (b *Blob) GetIDAtIndex(i int) string {
	if i < 0 || i >= len(b.header) {
		return ""
	}
	return b.header[i].id
}

// GetByIndex returns the data of the ith item in the blob. If the index is out
// of bounds, nil is returned and found will be false.
func (b *Blob) GetByIndex(i int) (data []byte, found bool) {
	if i < 0 || i >= len(b.header) {
		return
	}
	data = b.data[b.header[i].start:b.header[i].end]
	found = true
	return
}

// Write writes the whole binary blob to the given writer.
//
// Format: the data is structured as follows, numbers are encoded in little
// endian byte order:
// 1. Header length in bytes uint32, this is the overall length off the header,
//    starting after this uint32
// 2. Header: consists of consecutive items, each of which are structured as
//    follows:
//  2.1. ID length in bytes, uint16 giving the length of the following ID string
//  2.2. ID, this is a string
//  2.3. Data length, this uint64 gives the length of the data for this item in
//       bytes
// 3. Data, it starts directly after the header so the offset into the overall
// file is the header lenght plus 4 bytes for the header header length itselfe,
// which is a uint32.
// For each item only the length is stored, the offset into the data can be
// computed by summing up the lengths of the items coming before that.
func (b *Blob) Write(w io.Writer) (err error) {
	buffer := bytes.NewBuffer(nil)
	for i := range b.header {
		// first write the ID length and then the ID
		err = binary.Write(buffer, byteOrder, uint16(len(b.header[i].id)))
		if err != nil {
			err = errors.New("writing blob header id length: " + err.Error())
			return
		}
		_, err = buffer.Write([]byte(b.header[i].id))
		if err != nil {
			err = errors.New("writing blob header id: " + err.Error())
			return
		}

		length := b.header[i].end - b.header[i].start
		err = binary.Write(buffer, byteOrder, length)
		if err != nil {
			err = errors.New("writing blob header data length: " + err.Error())
			return
		}
	}
	// write the header length
	err = binary.Write(w, byteOrder, uint32(buffer.Len()))
	if err != nil {
		err = errors.New("writing blob header length: " + err.Error())
		return
	}
	// write the actual header data
	_, err = w.Write(buffer.Bytes())
	if err != nil {
		err = errors.New("writing blob header: " + err.Error())
		return
	}
	// write the data
	_, err = w.Write(b.data)
	if err != nil {
		err = errors.New("writing blob data: " + err.Error())
		return
	}
	return nil
}

var byteOrder = binary.LittleEndian

// Read reads a binary blob from the given reader. If a read fails it returns
// that read's error. If the error is non-nil the returned blob is nil.
// See Blob.Write for a description of the data format.
func Read(r io.Reader) (blob *Blob, err error) {
	var b Blob

	// read header length
	var headerLength uint32
	err = binary.Read(r, byteOrder, &headerLength)
	if err != nil {
		err = errors.New("reading blob header length: " + err.Error())
		return
	}

	// read the actual header
	header := make([]byte, headerLength)
	_, err = r.Read(header)
	if err != nil {
		err = errors.New("reading blob header: " + err.Error())
		return
	}

	// dissect the header, keeping track of the overall data length
	var overallDataLength uint64
	var dataLength uint64
	var idLength uint16
	headerReader := bytes.NewBuffer(header)
	for headerReader.Len() > 0 {
		err = binary.Read(headerReader, byteOrder, &idLength)
		if err != nil {
			err = errors.New("reading blob header id length: " + err.Error())
			return
		}

		id := string(headerReader.Next(int(idLength)))
		if len(id) != int(idLength) {
			err = errors.New("reading blob header id: unexpected EOF")
			return
		}

		err = binary.Read(headerReader, byteOrder, &dataLength)
		if err != nil {
			err = errors.New("reading blob header data length: " + err.Error())
			return
		}

		b.header = append(b.header, indexItem{
			id,
			overallDataLength,
			overallDataLength + dataLength,
		})

		overallDataLength += dataLength
	}

	if overallDataLength > 0 {
		b.data = make([]byte, overallDataLength)
		_, err = io.ReadFull(r, b.data)
		if err != nil {
			err = errors.New("reading blob data: " + err.Error())
			return
		}
	}

	blob = &b
	return
}
<commit_msg>Improved the comments.<commit_after>package blob

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
)

type Blob struct {
	header []indexItem
	data   []byte
}

type indexItem struct {
	id    string
	start uint64
	end   uint64
}

// New creates an empty blob. You can add resources to it using Append. After
// adding all resources, you can call Write to write it to a file for example.
func New() *Blob {
	return &Blob{}
}

// ItemCount returns the number of blob items. When using GetIDAtIndex or
// GetByIndex, valid inidices range from 0 to ItemCount()-1.
func (b *Blob) ItemCount() int {
	return len(b.header)
}

// Append adds the given data at the end of the blob.
func (b *Blob) Append(id string, data []byte) {
	b.header = append(
		b.header,
		indexItem{
			id,
			uint64(len(b.data)),
			uint64(len(b.data) + len(data)),
		},
	)
	b.data = append(b.data, data...)
}

// GetByID searches the blob for an entry with the given ID and returns the
// first one found. If there is no entry with the given ID, data will be nil and
// found will be false.
func (b *Blob) GetByID(id string) (data []byte, found bool) {
	for i := range b.header {
		if b.header[i].id == id {
			data = b.data[b.header[i].start:b.header[i].end]
			found = true
			return
		}
	}
	return
}

// GetIDAtIndex returns the ID of the entry at index i or the empty string if
// the given index is out of bounds. See ItemCount for the number of items.
func (b *Blob) GetIDAtIndex(i int) string {
	if i < 0 || i >= len(b.header) {
		return ""
	}
	return b.header[i].id
}

// GetByIndex returns the data of the entry at index i. If the index is out of
// bounds, data will be nil and found will be false. See ItemCount for the
// number of items.
func (b *Blob) GetByIndex(i int) (data []byte, found bool) {
	if i < 0 || i >= len(b.header) {
		return
	}
	data = b.data[b.header[i].start:b.header[i].end]
	found = true
	return
}

// Write writes the whole binary blob to the given writer.
//
// Format (all numbers are encoded in little endian byte order):
// - uint32: Header length in bytes, of the header starting after this number
// - header starts here, it consists of mulitple entries structured as follows:
// - uint16 ID length in bytes, length of the following ID
// - string ID, UTF-8 encoded
// - uint64 data length in bytes, of the data associated with this ID
// - header ends here, the binary data starts directly after the header
// - all data byte slices are simply appended and written as one blob after the
// header
//
// Note that the header does not store offsets into the data explicitly, it
// only stores the length of each item so the offset can be computed from the
// cumulative sum of all data lengths of items that come before it.
func (b *Blob) Write(w io.Writer) (err error) {
	buffer := bytes.NewBuffer(nil)
	for i := range b.header {
		// first write the ID length and then the ID
		err = binary.Write(buffer, byteOrder, uint16(len(b.header[i].id)))
		if err != nil {
			err = errors.New("writing blob header id length: " + err.Error())
			return
		}
		_, err = buffer.Write([]byte(b.header[i].id))
		if err != nil {
			err = errors.New("writing blob header id: " + err.Error())
			return
		}

		length := b.header[i].end - b.header[i].start
		err = binary.Write(buffer, byteOrder, length)
		if err != nil {
			err = errors.New("writing blob header data length: " + err.Error())
			return
		}
	}
	// write the header length
	err = binary.Write(w, byteOrder, uint32(buffer.Len()))
	if err != nil {
		err = errors.New("writing blob header length: " + err.Error())
		return
	}
	// write the actual header data
	_, err = w.Write(buffer.Bytes())
	if err != nil {
		err = errors.New("writing blob header: " + err.Error())
		return
	}
	// write the data
	_, err = w.Write(b.data)
	if err != nil {
		err = errors.New("writing blob data: " + err.Error())
		return
	}
	return nil
}

var byteOrder = binary.LittleEndian

// Read reads a binary blob from the given reader. If an error occurs, the
// returned blob will be nil. See Write for a description of the data format.
func Read(r io.Reader) (blob *Blob, err error) {
	var b Blob

	// read header length
	var headerLength uint32
	err = binary.Read(r, byteOrder, &headerLength)
	if err != nil {
		err = errors.New("reading blob header length: " + err.Error())
		return
	}

	// read the actual header
	header := make([]byte, headerLength)
	_, err = r.Read(header)
	if err != nil {
		err = errors.New("reading blob header: " + err.Error())
		return
	}

	// dissect the header, keeping track of the overall data length
	var overallDataLength uint64
	var dataLength uint64
	var idLength uint16
	headerReader := bytes.NewBuffer(header)
	for headerReader.Len() > 0 {
		err = binary.Read(headerReader, byteOrder, &idLength)
		if err != nil {
			err = errors.New("reading blob header id length: " + err.Error())
			return
		}

		id := string(headerReader.Next(int(idLength)))
		if len(id) != int(idLength) {
			err = errors.New("reading blob header id: unexpected EOF")
			return
		}

		err = binary.Read(headerReader, byteOrder, &dataLength)
		if err != nil {
			err = errors.New("reading blob header data length: " + err.Error())
			return
		}

		b.header = append(b.header, indexItem{
			id,
			overallDataLength,
			overallDataLength + dataLength,
		})

		overallDataLength += dataLength
	}

	if overallDataLength > 0 {
		b.data = make([]byte, overallDataLength)
		_, err = io.ReadFull(r, b.data)
		if err != nil {
			err = errors.New("reading blob data: " + err.Error())
			return
		}
	}

	blob = &b
	return
}
<|endoftext|>


<commit_before>package boardgame

import (
	"errors"
)

//ComputedProperties represents a collection of compute properties for a given
//state.
type ComputedProperties interface {
	PropertyReader
}

type ComputedPropertiesConfig struct {
	Properties map[string]ComputedPropertyDefinition
}

type ShadowPlayerState struct {
	PropertyReader
}

//ShadowState is an object roughly shaped like a State, but where instead of
//underlying types it has PropertyReaders. Passed in to the Compute method of
//a ComputedProperty, based on the dependencies they define.
type ShadowState struct {
	Game    PropertyReader
	Players []*ShadowPlayerState
}

type ComputedPropertyDefinition struct {
	Dependencies []StatePropertyRef
	//The thing we expect to be able to cast the result of Compute to.
	PropType PropertyType
	Compute  func(shadow *ShadowState) (interface{}, error)
}

type StateGroupType int

const (
	StateGroupGame StateGroupType = iota
	StateGroupPlayer
)

type StatePropertyRef struct {
	Group    StateGroupType
	PropName string
}

//The private impl for ComputedProperties
type computedPropertiesImpl struct {
	*computedPropertiesBag
	state  *State
	config *ComputedPropertiesConfig
}

type computedPropertiesBag struct {
	unknownProps map[string]interface{}
	intProps     map[string]int
	boolProps    map[string]bool
	stringProps  map[string]string
}

//Computed returns the computed properties for this state.
func (s *State) Computed() ComputedProperties {
	if s.computed == nil {
		config := s.delegate.ComputedPropertiesConfig()
		s.computed = &computedPropertiesImpl{
			newComputedPropertiesBag(),
			s,
			config,
		}
	}
	return s.computed
}

func newComputedPropertiesBag() *computedPropertiesBag {
	return &computedPropertiesBag{
		unknownProps: make(map[string]interface{}),
		intProps:     make(map[string]int),
		boolProps:    make(map[string]bool),
		stringProps:  make(map[string]string),
	}
}

func (c *computedPropertiesBag) Props() map[string]PropertyType {
	result := make(map[string]PropertyType)

	//TODO: memoize this

	for key, _ := range c.unknownProps {
		//TODO: shouldn't this be TypeUnknown?
		result[key] = TypeIllegal
	}

	for key, _ := range c.intProps {
		result[key] = TypeInt
	}

	for key, _ := range c.boolProps {
		result[key] = TypeBool
	}

	for key, _ := range c.stringProps {
		result[key] = TypeString
	}

	return result
}

func (c *computedPropertiesBag) GrowableStackProp(name string) (*GrowableStack, error) {
	//We don't (yet?) support growable stack computed props
	return nil, errors.New("No such growable stack prop")
}

func (c *computedPropertiesBag) SizedStackProp(name string) (*SizedStack, error) {
	//We don't (yet?) support SizedStackProps.
	return nil, errors.New("No such sized stack prop")
}

func (c *computedPropertiesBag) IntProp(name string) (int, error) {
	result, ok := c.intProps[name]

	if !ok {
		return 0, errors.New("No such int prop")
	}

	return result, nil
}

func (c *computedPropertiesBag) BoolProp(name string) (bool, error) {
	result, ok := c.boolProps[name]

	if !ok {
		return false, errors.New("No such bool prop")
	}

	return result, nil
}

func (c *computedPropertiesBag) StringProp(name string) (string, error) {
	result, ok := c.stringProps[name]

	if !ok {
		return "", errors.New("No such string prop")
	}

	return result, nil
}

func (c *computedPropertiesBag) Prop(name string) (interface{}, error) {
	props := c.Props()

	propType, ok := props[name]

	if !ok {
		return nil, errors.New("No prop with that name")
	}

	switch propType {
	case TypeString:
		return c.StringProp(name)
	case TypeBool:
		return c.BoolProp(name)
	case TypeInt:
		return c.IntProp(name)
	}

	val, ok := c.unknownProps[name]

	if !ok {
		return nil, errors.New("No such unknown prop")
	}

	return val, nil
}

func (c *computedPropertiesBag) SetIntProp(name string, value int) error {
	c.intProps[name] = value
	return nil
}

func (c *computedPropertiesBag) SetBoolProp(name string, value bool) error {
	c.boolProps[name] = value
	return nil
}

func (c *computedPropertiesBag) SetStringProp(name string, value string) error {
	c.stringProps[name] = value
	return nil
}

func (c *computedPropertiesBag) SetGrowableStackProp(name string, value *GrowableStack) error {
	return errors.New("We don't currently support growable stacks")
}

func (c *computedPropertiesBag) SetSizedStackProp(name string, value *SizedStack) error {
	return errors.New("We don't currently support sized stacks")
}

func (c *computedPropertiesBag) SetProp(name string, value interface{}) error {
	c.unknownProps[name] = value
	return nil
}
<commit_msg>ComputedPropertyDefinition gets the beginnings of a compute() method. Part of #146.<commit_after>package boardgame

import (
	"errors"
)

//ComputedProperties represents a collection of compute properties for a given
//state.
type ComputedProperties interface {
	PropertyReader
}

type ComputedPropertiesConfig struct {
	Properties map[string]ComputedPropertyDefinition
}

type ShadowPlayerState struct {
	PropertyReader
}

//ShadowState is an object roughly shaped like a State, but where instead of
//underlying types it has PropertyReaders. Passed in to the Compute method of
//a ComputedProperty, based on the dependencies they define.
type ShadowState struct {
	Game    PropertyReader
	Players []*ShadowPlayerState
}

type ComputedPropertyDefinition struct {
	Dependencies []StatePropertyRef
	//The thing we expect to be able to cast the result of Compute to.
	PropType PropertyType
	Compute  func(shadow *ShadowState) (interface{}, error)
}

type StateGroupType int

const (
	StateGroupGame StateGroupType = iota
	StateGroupPlayer
)

type StatePropertyRef struct {
	Group    StateGroupType
	PropName string
}

//The private impl for ComputedProperties
type computedPropertiesImpl struct {
	*computedPropertiesBag
	state  *State
	config *ComputedPropertiesConfig
}

type computedPropertiesBag struct {
	unknownProps map[string]interface{}
	intProps     map[string]int
	boolProps    map[string]bool
	stringProps  map[string]string
}

//Computed returns the computed properties for this state.
func (s *State) Computed() ComputedProperties {
	if s.computed == nil {
		config := s.delegate.ComputedPropertiesConfig()
		s.computed = &computedPropertiesImpl{
			newComputedPropertiesBag(),
			s,
			config,
		}
	}
	return s.computed
}

func (c *ComputedPropertyDefinition) compute(state *State) (interface{}, error) {

	//First, prepare a shadow state with all of the dependencies.

	players := make([]*ShadowPlayerState, len(state.Players))

	for i := 0; i < len(state.Players); i++ {
		players[i] = &ShadowPlayerState{newComputedPropertiesBag()}
	}

	shadow := &ShadowState{
		Game:    newComputedPropertiesBag(),
		Players: players,
	}

	//TODO: actually put in the dependency values

	return c.Compute(shadow)

}

func newComputedPropertiesBag() *computedPropertiesBag {
	return &computedPropertiesBag{
		unknownProps: make(map[string]interface{}),
		intProps:     make(map[string]int),
		boolProps:    make(map[string]bool),
		stringProps:  make(map[string]string),
	}
}

func (c *computedPropertiesBag) Props() map[string]PropertyType {
	result := make(map[string]PropertyType)

	//TODO: memoize this

	for key, _ := range c.unknownProps {
		//TODO: shouldn't this be TypeUnknown?
		result[key] = TypeIllegal
	}

	for key, _ := range c.intProps {
		result[key] = TypeInt
	}

	for key, _ := range c.boolProps {
		result[key] = TypeBool
	}

	for key, _ := range c.stringProps {
		result[key] = TypeString
	}

	return result
}

func (c *computedPropertiesBag) GrowableStackProp(name string) (*GrowableStack, error) {
	//We don't (yet?) support growable stack computed props
	return nil, errors.New("No such growable stack prop")
}

func (c *computedPropertiesBag) SizedStackProp(name string) (*SizedStack, error) {
	//We don't (yet?) support SizedStackProps.
	return nil, errors.New("No such sized stack prop")
}

func (c *computedPropertiesBag) IntProp(name string) (int, error) {
	result, ok := c.intProps[name]

	if !ok {
		return 0, errors.New("No such int prop")
	}

	return result, nil
}

func (c *computedPropertiesBag) BoolProp(name string) (bool, error) {
	result, ok := c.boolProps[name]

	if !ok {
		return false, errors.New("No such bool prop")
	}

	return result, nil
}

func (c *computedPropertiesBag) StringProp(name string) (string, error) {
	result, ok := c.stringProps[name]

	if !ok {
		return "", errors.New("No such string prop")
	}

	return result, nil
}

func (c *computedPropertiesBag) Prop(name string) (interface{}, error) {
	props := c.Props()

	propType, ok := props[name]

	if !ok {
		return nil, errors.New("No prop with that name")
	}

	switch propType {
	case TypeString:
		return c.StringProp(name)
	case TypeBool:
		return c.BoolProp(name)
	case TypeInt:
		return c.IntProp(name)
	}

	val, ok := c.unknownProps[name]

	if !ok {
		return nil, errors.New("No such unknown prop")
	}

	return val, nil
}

func (c *computedPropertiesBag) SetIntProp(name string, value int) error {
	c.intProps[name] = value
	return nil
}

func (c *computedPropertiesBag) SetBoolProp(name string, value bool) error {
	c.boolProps[name] = value
	return nil
}

func (c *computedPropertiesBag) SetStringProp(name string, value string) error {
	c.stringProps[name] = value
	return nil
}

func (c *computedPropertiesBag) SetGrowableStackProp(name string, value *GrowableStack) error {
	return errors.New("We don't currently support growable stacks")
}

func (c *computedPropertiesBag) SetSizedStackProp(name string, value *SizedStack) error {
	return errors.New("We don't currently support sized stacks")
}

func (c *computedPropertiesBag) SetProp(name string, value interface{}) error {
	c.unknownProps[name] = value
	return nil
}
<|endoftext|>