<commit_before>// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package codehost defines the interface implemented by a code hosting source,
// along with support code for use by implementations.
package codehost

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	exec "internal/execabs"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"cmd/go/internal/cfg"
	"cmd/go/internal/lockedfile"
	"cmd/go/internal/str"
)

// Downloaded size limits.
const (
	MaxGoMod   = 16 << 20  // maximum size of go.mod file
	MaxLICENSE = 16 << 20  // maximum size of LICENSE file
	MaxZipFile = 500 << 20 // maximum size of downloaded zip file
)

// A Repo represents a code hosting source.
// Typical implementations include local version control repositories,
// remote version control servers, and code hosting sites.
// A Repo must be safe for simultaneous use by multiple goroutines.
type Repo interface {
	// List lists all tags with the given prefix.
	Tags(prefix string) (tags []string, err error)

	// Stat returns information about the revision rev.
	// A revision can be any identifier known to the underlying service:
	// commit hash, branch, tag, and so on.
	Stat(rev string) (*RevInfo, error)

	// Latest returns the latest revision on the default branch,
	// whatever that means in the underlying implementation.
	Latest() (*RevInfo, error)

	// ReadFile reads the given file in the file tree corresponding to revision rev.
	// It should refuse to read more than maxSize bytes.
	//
	// If the requested file does not exist it should return an error for which
	// os.IsNotExist(err) returns true.
	ReadFile(rev, file string, maxSize int64) (data []byte, err error)

	// ReadZip downloads a zip file for the subdir subdirectory
	// of the given revision to a new file in a given temporary directory.
	// It should refuse to read more than maxSize bytes.
	// It returns a ReadCloser for a streamed copy of the zip file.
	// All files in the zip file are expected to be
	// nested in a single top-level directory, whose name is not specified.
	ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error)

	// RecentTag returns the most recent tag on rev or one of its predecessors
	// with the given prefix. allowed may be used to filter out unwanted versions.
	RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error)

	// DescendsFrom reports whether rev or any of its ancestors has the given tag.
	//
	// DescendsFrom must return true for any tag returned by RecentTag for the
	// same revision.
	DescendsFrom(rev, tag string) (bool, error)
}

// A Rev describes a single revision in a source code repository.
type RevInfo struct {
	Name    string    // complete ID in underlying repository
	Short   string    // shortened ID, for use in pseudo-version
	Version string    // version used in lookup
	Time    time.Time // commit time
	Tags    []string  // known tags for commit
}

// A FileRev describes the result of reading a file at a given revision.
type FileRev struct {
	Rev  string // requested revision
	Data []byte // file data
	Err  error  // error if any; os.IsNotExist(Err)==true if rev exists but file does not exist in that rev
}

// UnknownRevisionError is an error equivalent to fs.ErrNotExist, but for a
// revision rather than a file.
type UnknownRevisionError struct {
	Rev string
}

func (e *UnknownRevisionError) Error() string {
	return "unknown revision " + e.Rev
}
func (UnknownRevisionError) Is(err error) bool {
	return err == fs.ErrNotExist
}

// ErrNoCommits is an error equivalent to fs.ErrNotExist indicating that a given
// repository or module contains no commits.
var ErrNoCommits error = noCommitsError{}

type noCommitsError struct{}

func (noCommitsError) Error() string {
	return "no commits"
}
func (noCommitsError) Is(err error) bool {
	return err == fs.ErrNotExist
}

// AllHex reports whether the revision rev is entirely lower-case hexadecimal digits.
func AllHex(rev string) bool {
	for i := 0; i < len(rev); i++ {
		c := rev[i]
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' {
			continue
		}
		return false
	}
	return true
}

// ShortenSHA1 shortens a SHA1 hash (40 hex digits) to the canonical length
// used in pseudo-versions (12 hex digits).
func ShortenSHA1(rev string) string {
	if AllHex(rev) && len(rev) == 40 {
		return rev[:12]
	}
	return rev
}

// WorkDir returns the name of the cached work directory to use for the
// given repository type and name.
func WorkDir(typ, name string) (dir, lockfile string, err error) {
	if cfg.GOMODCACHE == "" {
		return "", "", fmt.Errorf("neither GOPATH nor GOMODCACHE are set")
	}

	// We name the work directory for the SHA256 hash of the type and name.
	// We intentionally avoid the actual name both because of possible
	// conflicts with valid file system paths and because we want to ensure
	// that one checkout is never nested inside another. That nesting has
	// led to security problems in the past.
	if strings.Contains(typ, ":") {
		return "", "", fmt.Errorf("codehost.WorkDir: type cannot contain colon")
	}
	key := typ + ":" + name
	dir = filepath.Join(cfg.GOMODCACHE, "cache/vcs", fmt.Sprintf("%x", sha256.Sum256([]byte(key))))

	if cfg.BuildX {
		fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", filepath.Dir(dir), typ, name)
	}
	if err := os.MkdirAll(filepath.Dir(dir), 0777); err != nil {
		return "", "", err
	}

	lockfile = dir + ".lock"
	if cfg.BuildX {
		fmt.Fprintf(os.Stderr, "# lock %s", lockfile)
	}

	unlock, err := lockedfile.MutexAt(lockfile).Lock()
	if err != nil {
		return "", "", fmt.Errorf("codehost.WorkDir: can't find or create lock file: %v", err)
	}
	defer unlock()

	data, err := os.ReadFile(dir + ".info")
	info, err2 := os.Stat(dir)
	if err == nil && err2 == nil && info.IsDir() {
		// Info file and directory both already exist: reuse.
		have := strings.TrimSuffix(string(data), "\n")
		if have != key {
			return "", "", fmt.Errorf("%s exists with wrong content (have %q want %q)", dir+".info", have, key)
		}
		if cfg.BuildX {
			fmt.Fprintf(os.Stderr, "# %s for %s %s\n", dir, typ, name)
		}
		return dir, lockfile, nil
	}

	// Info file or directory missing. Start from scratch.
	if cfg.BuildX {
		fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", dir, typ, name)
	}
	os.RemoveAll(dir)
	if err := os.MkdirAll(dir, 0777); err != nil {
		return "", "", err
	}
	if err := os.WriteFile(dir+".info", []byte(key), 0666); err != nil {
		os.RemoveAll(dir)
		return "", "", err
	}
	return dir, lockfile, nil
}

type RunError struct {
	Cmd      string
	Err      error
	Stderr   []byte
	HelpText string
}

func (e *RunError) Error() string {
	text := e.Cmd + ": " + e.Err.Error()
	stderr := bytes.TrimRight(e.Stderr, "\n")
	if len(stderr) > 0 {
		text += ":\n\t" + strings.ReplaceAll(string(stderr), "\n", "\n\t")
	}
	if len(e.HelpText) > 0 {
		text += "\n" + e.HelpText
	}
	return text
}

var dirLock sync.Map

// Run runs the command line in the given directory
// (an empty dir means the current directory).
// It returns the standard output and, for a non-zero exit,
// a *RunError indicating the command, exit status, and standard error.
// Standard error is unavailable for commands that exit successfully.
func Run(dir string, cmdline ...any) ([]byte, error) {
	return RunWithStdin(dir, nil, cmdline...)
}

// bashQuoter escapes characters that have special meaning in double-quoted strings in the bash shell.
// See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html.
var bashQuoter = strings.NewReplacer(`"`, `\"`, `$`, `\$`, "`", "\\`", `\`, `\\`)

func RunWithStdin(dir string, stdin io.Reader, cmdline ...any) ([]byte, error) {
	if dir != "" {
		muIface, ok := dirLock.Load(dir)
		if !ok {
			muIface, _ = dirLock.LoadOrStore(dir, new(sync.Mutex))
		}
		mu := muIface.(*sync.Mutex)
		mu.Lock()
		defer mu.Unlock()
	}

	cmd := str.StringList(cmdline...)
	if os.Getenv("TESTGOVCS") == "panic" {
		panic(fmt.Sprintf("use of vcs: %v", cmd))
	}
	if cfg.BuildX {
		text := new(strings.Builder)
		if dir != "" {
			text.WriteString("cd ")
			text.WriteString(dir)
			text.WriteString("; ")
		}
		for i, arg := range cmd {
			if i > 0 {
				text.WriteByte(' ')
			}
			switch {
			case strings.ContainsAny(arg, "'"):
				// Quote args that could be mistaken for quoted args.
				text.WriteByte('"')
				text.WriteString(bashQuoter.Replace(arg))
				text.WriteByte('"')
			case strings.ContainsAny(arg, "$`\\*?[\"\t\n\v\f\r \u0085\u00a0"):
				// Quote args that contain special characters, glob patterns, or spaces.
				text.WriteByte('\'')
				text.WriteString(arg)
				text.WriteByte('\'')
			default:
				text.WriteString(arg)
			}
		}
		fmt.Fprintf(os.Stderr, "%s\n", text)
		start := time.Now()
		defer func() {
			fmt.Fprintf(os.Stderr, "%.3fs # %s\n", time.Since(start).Seconds(), text)
		}()
	}
	// TODO: Impose limits on command output size.
	// TODO: Set environment to get English error messages.
	var stderr bytes.Buffer
	var stdout bytes.Buffer
	c := exec.Command(cmd[0], cmd[1:]...)
	c.Dir = dir
	c.Stdin = stdin
	c.Stderr = &stderr
	c.Stdout = &stdout
	err := c.Run()
	if err != nil {
		err = &RunError{Cmd: strings.Join(cmd, " ") + " in " + dir, Stderr: stderr.Bytes(), Err: err}
	}
	return stdout.Bytes(), err
}
<commit_msg>cmd/go/internal/modfetch: remove unused FileRev struct<commit_after>// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package codehost defines the interface implemented by a code hosting source,
// along with support code for use by implementations.
package codehost

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	exec "internal/execabs"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"cmd/go/internal/cfg"
	"cmd/go/internal/lockedfile"
	"cmd/go/internal/str"
)

// Downloaded size limits.
const (
	MaxGoMod   = 16 << 20  // maximum size of go.mod file
	MaxLICENSE = 16 << 20  // maximum size of LICENSE file
	MaxZipFile = 500 << 20 // maximum size of downloaded zip file
)

// A Repo represents a code hosting source.
// Typical implementations include local version control repositories,
// remote version control servers, and code hosting sites.
// A Repo must be safe for simultaneous use by multiple goroutines.
type Repo interface {
	// List lists all tags with the given prefix.
	Tags(prefix string) (tags []string, err error)

	// Stat returns information about the revision rev.
	// A revision can be any identifier known to the underlying service:
	// commit hash, branch, tag, and so on.
	Stat(rev string) (*RevInfo, error)

	// Latest returns the latest revision on the default branch,
	// whatever that means in the underlying implementation.
	Latest() (*RevInfo, error)

	// ReadFile reads the given file in the file tree corresponding to revision rev.
	// It should refuse to read more than maxSize bytes.
	//
	// If the requested file does not exist it should return an error for which
	// os.IsNotExist(err) returns true.
	ReadFile(rev, file string, maxSize int64) (data []byte, err error)

	// ReadZip downloads a zip file for the subdir subdirectory
	// of the given revision to a new file in a given temporary directory.
	// It should refuse to read more than maxSize bytes.
	// It returns a ReadCloser for a streamed copy of the zip file.
	// All files in the zip file are expected to be
	// nested in a single top-level directory, whose name is not specified.
	ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error)

	// RecentTag returns the most recent tag on rev or one of its predecessors
	// with the given prefix. allowed may be used to filter out unwanted versions.
	RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error)

	// DescendsFrom reports whether rev or any of its ancestors has the given tag.
	//
	// DescendsFrom must return true for any tag returned by RecentTag for the
	// same revision.
	DescendsFrom(rev, tag string) (bool, error)
}

// A Rev describes a single revision in a source code repository.
type RevInfo struct {
	Name    string    // complete ID in underlying repository
	Short   string    // shortened ID, for use in pseudo-version
	Version string    // version used in lookup
	Time    time.Time // commit time
	Tags    []string  // known tags for commit
}

// UnknownRevisionError is an error equivalent to fs.ErrNotExist, but for a
// revision rather than a file.
type UnknownRevisionError struct {
	Rev string
}

func (e *UnknownRevisionError) Error() string {
	return "unknown revision " + e.Rev
}
func (UnknownRevisionError) Is(err error) bool {
	return err == fs.ErrNotExist
}

// ErrNoCommits is an error equivalent to fs.ErrNotExist indicating that a given
// repository or module contains no commits.
var ErrNoCommits error = noCommitsError{}

type noCommitsError struct{}

func (noCommitsError) Error() string {
	return "no commits"
}
func (noCommitsError) Is(err error) bool {
	return err == fs.ErrNotExist
}

// AllHex reports whether the revision rev is entirely lower-case hexadecimal digits.
func AllHex(rev string) bool {
	for i := 0; i < len(rev); i++ {
		c := rev[i]
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' {
			continue
		}
		return false
	}
	return true
}

// ShortenSHA1 shortens a SHA1 hash (40 hex digits) to the canonical length
// used in pseudo-versions (12 hex digits).
func ShortenSHA1(rev string) string {
	if AllHex(rev) && len(rev) == 40 {
		return rev[:12]
	}
	return rev
}

// WorkDir returns the name of the cached work directory to use for the
// given repository type and name.
func WorkDir(typ, name string) (dir, lockfile string, err error) {
	if cfg.GOMODCACHE == "" {
		return "", "", fmt.Errorf("neither GOPATH nor GOMODCACHE are set")
	}

	// We name the work directory for the SHA256 hash of the type and name.
	// We intentionally avoid the actual name both because of possible
	// conflicts with valid file system paths and because we want to ensure
	// that one checkout is never nested inside another. That nesting has
	// led to security problems in the past.
	if strings.Contains(typ, ":") {
		return "", "", fmt.Errorf("codehost.WorkDir: type cannot contain colon")
	}
	key := typ + ":" + name
	dir = filepath.Join(cfg.GOMODCACHE, "cache/vcs", fmt.Sprintf("%x", sha256.Sum256([]byte(key))))

	if cfg.BuildX {
		fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", filepath.Dir(dir), typ, name)
	}
	if err := os.MkdirAll(filepath.Dir(dir), 0777); err != nil {
		return "", "", err
	}

	lockfile = dir + ".lock"
	if cfg.BuildX {
		fmt.Fprintf(os.Stderr, "# lock %s", lockfile)
	}

	unlock, err := lockedfile.MutexAt(lockfile).Lock()
	if err != nil {
		return "", "", fmt.Errorf("codehost.WorkDir: can't find or create lock file: %v", err)
	}
	defer unlock()

	data, err := os.ReadFile(dir + ".info")
	info, err2 := os.Stat(dir)
	if err == nil && err2 == nil && info.IsDir() {
		// Info file and directory both already exist: reuse.
		have := strings.TrimSuffix(string(data), "\n")
		if have != key {
			return "", "", fmt.Errorf("%s exists with wrong content (have %q want %q)", dir+".info", have, key)
		}
		if cfg.BuildX {
			fmt.Fprintf(os.Stderr, "# %s for %s %s\n", dir, typ, name)
		}
		return dir, lockfile, nil
	}

	// Info file or directory missing. Start from scratch.
	if cfg.BuildX {
		fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", dir, typ, name)
	}
	os.RemoveAll(dir)
	if err := os.MkdirAll(dir, 0777); err != nil {
		return "", "", err
	}
	if err := os.WriteFile(dir+".info", []byte(key), 0666); err != nil {
		os.RemoveAll(dir)
		return "", "", err
	}
	return dir, lockfile, nil
}

type RunError struct {
	Cmd      string
	Err      error
	Stderr   []byte
	HelpText string
}

func (e *RunError) Error() string {
	text := e.Cmd + ": " + e.Err.Error()
	stderr := bytes.TrimRight(e.Stderr, "\n")
	if len(stderr) > 0 {
		text += ":\n\t" + strings.ReplaceAll(string(stderr), "\n", "\n\t")
	}
	if len(e.HelpText) > 0 {
		text += "\n" + e.HelpText
	}
	return text
}

var dirLock sync.Map

// Run runs the command line in the given directory
// (an empty dir means the current directory).
// It returns the standard output and, for a non-zero exit,
// a *RunError indicating the command, exit status, and standard error.
// Standard error is unavailable for commands that exit successfully.
func Run(dir string, cmdline ...any) ([]byte, error) {
	return RunWithStdin(dir, nil, cmdline...)
}

// bashQuoter escapes characters that have special meaning in double-quoted strings in the bash shell.
// See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html.
var bashQuoter = strings.NewReplacer(`"`, `\"`, `$`, `\$`, "`", "\\`", `\`, `\\`)

func RunWithStdin(dir string, stdin io.Reader, cmdline ...any) ([]byte, error) {
	if dir != "" {
		muIface, ok := dirLock.Load(dir)
		if !ok {
			muIface, _ = dirLock.LoadOrStore(dir, new(sync.Mutex))
		}
		mu := muIface.(*sync.Mutex)
		mu.Lock()
		defer mu.Unlock()
	}

	cmd := str.StringList(cmdline...)
	if os.Getenv("TESTGOVCS") == "panic" {
		panic(fmt.Sprintf("use of vcs: %v", cmd))
	}
	if cfg.BuildX {
		text := new(strings.Builder)
		if dir != "" {
			text.WriteString("cd ")
			text.WriteString(dir)
			text.WriteString("; ")
		}
		for i, arg := range cmd {
			if i > 0 {
				text.WriteByte(' ')
			}
			switch {
			case strings.ContainsAny(arg, "'"):
				// Quote args that could be mistaken for quoted args.
				text.WriteByte('"')
				text.WriteString(bashQuoter.Replace(arg))
				text.WriteByte('"')
			case strings.ContainsAny(arg, "$`\\*?[\"\t\n\v\f\r \u0085\u00a0"):
				// Quote args that contain special characters, glob patterns, or spaces.
				text.WriteByte('\'')
				text.WriteString(arg)
				text.WriteByte('\'')
			default:
				text.WriteString(arg)
			}
		}
		fmt.Fprintf(os.Stderr, "%s\n", text)
		start := time.Now()
		defer func() {
			fmt.Fprintf(os.Stderr, "%.3fs # %s\n", time.Since(start).Seconds(), text)
		}()
	}
	// TODO: Impose limits on command output size.
	// TODO: Set environment to get English error messages.
	var stderr bytes.Buffer
	var stdout bytes.Buffer
	c := exec.Command(cmd[0], cmd[1:]...)
	c.Dir = dir
	c.Stdin = stdin
	c.Stderr = &stderr
	c.Stdout = &stdout
	err := c.Run()
	if err != nil {
		err = &RunError{Cmd: strings.Join(cmd, " ") + " in " + dir, Stderr: stderr.Bytes(), Err: err}
	}
	return stdout.Bytes(), err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/VonC/godbg"
	"github.com/VonC/godbg/exit"
	"github.com/atotto/clipboard"
	"github.com/google/go-github/github"
)

var client *github.Client
var ex *exit.Exit
var pdbg *godbg.Pdbg

func init() {
	ex = exit.Default()
	if os.Getenv("dbg") != "" {
		pdbg = godbg.NewPdbg()
	} else {
		pdbg = godbg.NewPdbg(godbg.OptExcludes([]string{"/seec.go"}))
	}
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("Usage:       go run seec.go <sha1>")
		fmt.Println("       dbg=1 go run seec.go <sha1> for debug information")
		ex.Exit(0)
	}
	sha1 := os.Args[1]
	client = github.NewClient(nil)
	displayRateLimit()
	commit, _, err := client.Git.GetCommit("git", "git", sha1)
	if err != nil {
		fmt.Printf("Unable to get commit '%s': err '%v'\n", sha1, err)
		ex.Exit(1)
	}
	if len(commit.Parents) != 2 {
		fmt.Printf("Sha1 '%s' has '%d' parent(s) instead of 2\n", sha1, len(commit.Parents))
	}
	clogin := login(*commit.Author.Email, *commit.Author.Name)
	parent := commit.Parents[1]
	res := ""
	res = res + seeCommit(&parent, commit)
	res = res + fmt.Sprintf("(Merged by [%s -- `%s` --](https://github.com/%s) in [commit %s](https://github.com/git/git/commit/%s), %s)  ",
		*commit.Author.Name, clogin, clogin,
		sha1[:7], sha1, commit.Committer.Date.Format("02 Jan 2006"))
	fmt.Println(res)
	clipboard.WriteAll(res)
	fmt.Println("(Copied to the clipboard)")
	displayRateLimit()
}

func displayRateLimit() {
	rate, _, err := client.RateLimit()
	if err != nil {
		fmt.Printf("Error fetching rate limit: %#v\n\n", err)
	} else {
		const layout = "15:04pm (MST)"
		t := rate.Reset.Time
		ts := fmt.Sprintf("%s", t.Format(layout))
		fmt.Printf("\nAPI Rate Limit: %d/%d (reset at %s)\n", rate.Remaining, rate.Limit, ts)
	}
}

type commitsByAuthor struct {
	author   *github.CommitAuthor
	pcommits []*github.Commit
}

func (cba *commitsByAuthor) String() string {
	res := ""
	first := true
	for i, pcommit := range cba.pcommits {
		if !first {
			res = res + ", "
		}
		first = false
		if i == len(cba.pcommits)-1 && i > 0 {
			res = res + "and "
		}
		res = res + (*pcommit.SHA)[:7]
	}
	return fmt.Sprintf("%s=>%s", *cba.author.Name, res)
}

func seeCommit(parent, commit *github.Commit) string {
	var pcommit *github.Commit
	var err error
	for pcommit == nil {
		pcommit, _, err = client.Git.GetCommit("git", "git", *parent.SHA)
		if err != nil {
			fmt.Printf("Unable to get parent commit '%s': err '%v'\n", parent.SHA, err)
			ex.Exit(1)
		}
		// fmt.Printf("pcommit '%+v', len %d\n", pcommit, len(pcommit.Parents))
		if len(pcommit.Parents) == 2 {
			parent = &pcommit.Parents[1]
			pcommit = nil
		} else {
			break
		}
	}
	var commits = make(map[string]*commitsByAuthor)
	apcommit := pcommit
	if *pcommit.Author.Name == *commit.Author.Name {
		pdbg.Pdbgf("Same author '%s', so call checkParentCommits\nInitial message: '%s'", *pcommit.Author.Name, *commit.Message)
		apcommit = &pcommit.Parents[0]
	}
	commits = checkParentCommits(apcommit, *commit.Message)
	if len(commits) == 0 {
		pauthorname := *pcommit.Author.Name
		pcommitsByAuthor := &commitsByAuthor{pcommit.Author, []*github.Commit{pcommit}}
		commits[pauthorname] = pcommitsByAuthor
		pdbg.Pdbgf("Put single commit '%s' for author '%s'", pcommitsByAuthor, pauthorname)
	}
	res := ""
	for _, pcommitsByAuthor := range commits {
		author := pcommitsByAuthor.author
		pcommits := pcommitsByAuthor.pcommits
		plogin := login(*author.Email, *author.Name)
		first := true
		for i, pcommit := range pcommits {
			if first {
				res = "See "
			} else {
				res = res + ", "
			}
			first = false
			if i == len(pcommits)-1 && i > 0 {
				res = res + "and "
			}
			c := fmt.Sprintf("[commit %s](https://github.com/git/git/commit/%s) [%s]",
				(*pcommit.SHA)[:7], *pcommit.SHA, pcommit.Author.Date.Format("02 Jan 2006"))
			res = res + c
		}
		res = res + fmt.Sprintf(" by [%s (`%s`)](https://github.com/%s).  \n",
			*author.Name, plogin, plogin)
		// seec 777e75b60568b613e452ebbb30a1fb27c4fd7d8a, https://github.com/git/git/commit/777e75b60568b613e452ebbb30a1fb27c4fd7d8a
		res = collect(res, *pcommit.Message, "Test-adapted-from")
		// seec 6dec263333417738528089834bd8cda72017aa31, https://github.com/git/git/commit/6dec263333417738528089834bd8cda72017aa31
		// seec 324a9f41cbf96ad994efc3b20be239116eba0dae, https://github.com/git/git/commit/324a9f41cbf96ad994efc3b20be239116eba0dae
		res = collect(res, *pcommit.Message, "Helped-by")
	}
	return res
}

// for cases like commit a6be52e239df4d4a469a5324273f43a0695fe95d
func checkParentCommits(apcommit *github.Commit, commitmsg string) map[string]*commitsByAuthor {
	res := make(map[string]*commitsByAuthor)
	pcommit, _, err := client.Git.GetCommit("git", "git", *apcommit.SHA)
	if err != nil {
		fmt.Printf("Unable to get check parent commit '%s': err '%v'\n", *apcommit.SHA, err)
		ex.Exit(1)
	}
	pdbg.Pdbgf("pcommit %s", *pcommit.SHA)
	pcommitmsgs := strings.Split(*pcommit.Message, "\n")
	title := pcommitmsgs[0]
	pdbg.Pdbgf("title '%s'", title)
	if strings.Contains(commitmsg, title) {
		pauthorname := *pcommit.Author.Name
		pdbg.Pdbgf("pauthorname='%s' for '%v'", pauthorname, pcommit.Author)
		pcommitsByAuthor := res[pauthorname]
		if pcommitsByAuthor == nil {
			pcommitsByAuthor = &commitsByAuthor{pcommit.Author, []*github.Commit{}}
		}
		pcommitsByAuthor.pcommits = append(pcommitsByAuthor.pcommits, pcommit)
		res[pauthorname] = pcommitsByAuthor
		pdbg.Pdbgf("call checkParentCommits with parents '%+v', pca '%s' for '%s'",
			pcommit.Parents, pcommitsByAuthor.String(), pauthorname)
		ppcommits := checkParentCommits(&pcommit.Parents[0], commitmsg)
		for authorName, pcommitsByAuthor := range ppcommits {
			acommitsByAuthor := res[authorName]
			if acommitsByAuthor == nil {
				res[authorName] = pcommitsByAuthor
			} else {
				for _, pc := range pcommitsByAuthor.pcommits {
					acommitsByAuthor.pcommits = append(acommitsByAuthor.pcommits, pc)
				}
				res[authorName] = acommitsByAuthor
				pdbg.Pdbgf("Put commits '%s' for author '%s'", acommitsByAuthor.String(), authorName)
			}
		}
	}
	return res
}

func login(email string, name string) string {
	opts := &github.SearchOptions{Order: "desc"}
	var res *github.UsersSearchResult
	var err error
	if email != "" {
		res, _, err = client.Search.Users(email, opts)
		if err != nil {
			fmt.Printf("Unable to search user '%s': err '%v'", email, err)
			ex.Exit(1)
		}
	}
	if res == nil || *res.Total == 0 {
		name = strings.Replace(name, "-", " ", -1)
		res, _, err = client.Search.Users(name, opts)
		if err != nil {
			fmt.Printf("Unable to search user '%s': err '%v'", name, err)
			ex.Exit(1)
		}
	}
	if res == nil || *res.Total == 0 {
		var resIssues *github.IssuesSearchResult
		issueSearch := fmt.Sprintf(`"Signed-off-by: %s <%s>"`, name, email)
		resIssues, _, err = client.Search.Issues(issueSearch, opts)
		if err != nil {
			fmt.Printf("Unable to search issue '%s': err '%v'", issueSearch, err)
			ex.Exit(1)
		}
		if resIssues == nil || *resIssues.Total == 0 {
			return ""
		}
		issue := resIssues.Issues[0]
		return *issue.User.Login
	}
	if res == nil || *res.Total == 0 {
		return ""
	}
	return *res.Users[0].Login
}

func collect(res, msg, activity string) string {
	re := regexp.MustCompile(fmt.Sprintf(`%s:\s+([^<\r\n]+)\s+<([^>\r\n]+)>`, activity))
	activitymsg := activity + ": "
	first := true
	allresc := re.FindAllStringSubmatch(msg, -1)
	for i, resc := range allresc {
		dot := ""
		if len(resc) != 3 {
			continue
		}
		name := resc[1]
		email := resc[2]
		login := login(email, name)
		if !first {
			activitymsg = activitymsg + ", "
		}
		if i == len(allresc)-1 {
			dot = "."
			if i > 0 {
				activitymsg = activitymsg + "and "
			}
		}
		if login == "" {
			activitymsg = activitymsg + fmt.Sprintf("%s <%s>%s", name, email, dot)
			first = false
			continue
		}
		activitymsg = activitymsg + fmt.Sprintf("[%s (`%s`)](https://github.com/%s)%s", name, login, login, dot)
		first = false
	}
	if !first {
		res = res + activitymsg + "  \n"
	}
	return res
}
<commit_msg>seec.go: uses client.RateLimits() for Core and Search limits<commit_after>package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/VonC/godbg"
	"github.com/VonC/godbg/exit"
	"github.com/atotto/clipboard"
	"github.com/google/go-github/github"
)

var client *github.Client
var ex *exit.Exit
var pdbg *godbg.Pdbg

func init() {
	ex = exit.Default()
	if os.Getenv("dbg") != "" {
		pdbg = godbg.NewPdbg()
	} else {
		pdbg = godbg.NewPdbg(godbg.OptExcludes([]string{"/seec.go"}))
	}
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("Usage:       go run seec.go <sha1>")
		fmt.Println("       dbg=1 go run seec.go <sha1> for debug information")
		ex.Exit(0)
	}
	sha1 := os.Args[1]
	client = github.NewClient(nil)
	displayRateLimit()
	commit, _, err := client.Git.GetCommit("git", "git", sha1)
	if err != nil {
		fmt.Printf("Unable to get commit '%s': err '%v'\n", sha1, err)
		ex.Exit(1)
	}
	if len(commit.Parents) != 2 {
		fmt.Printf("Sha1 '%s' has '%d' parent(s) instead of 2\n", sha1, len(commit.Parents))
	}
	clogin := login(*commit.Author.Email, *commit.Author.Name)
	parent := commit.Parents[1]
	res := ""
	res = res + seeCommit(&parent, commit)
	res = res + fmt.Sprintf("(Merged by [%s -- `%s` --](https://github.com/%s) in [commit %s](https://github.com/git/git/commit/%s), %s)  ",
		*commit.Author.Name, clogin, clogin,
		sha1[:7], sha1, commit.Committer.Date.Format("02 Jan 2006"))
	fmt.Println(res)
	clipboard.WriteAll(res)
	fmt.Println("(Copied to the clipboard)")
	displayRateLimit()
}

func displayRateLimit() {
	rate, _, err := client.RateLimits()
	if err != nil {
		fmt.Printf("Error fetching rate limit: %#v\n\n", err)
	} else {
		const layout = "15:04pm (MST)"
		tc := rate.Core.Reset.Time
		tcs := fmt.Sprintf("%s", tc.Format(layout))
		ts := rate.Search.Reset.Time
		tss := fmt.Sprintf("%s", ts.Format(layout))
		fmt.Printf("\nAPI Rate Core Limit: %d/%d (reset at %s) - Search Limit: %d/%d (reset at %s)\n",
			rate.Core.Remaining, rate.Core.Limit, tcs,
			rate.Search.Remaining, rate.Search.Limit, tss)
	}
}

type commitsByAuthor struct {
	author   *github.CommitAuthor
	pcommits []*github.Commit
}

func (cba *commitsByAuthor) String() string {
	res := ""
	first := true
	for i, pcommit := range cba.pcommits {
		if !first {
			res = res + ", "
		}
		first = false
		if i == len(cba.pcommits)-1 && i > 0 {
			res = res + "and "
		}
		res = res + (*pcommit.SHA)[:7]
	}
	return fmt.Sprintf("%s=>%s", *cba.author.Name, res)
}

func seeCommit(parent, commit *github.Commit) string {
	var pcommit *github.Commit
	var err error
	for pcommit == nil {
		pcommit, _, err = client.Git.GetCommit("git", "git", *parent.SHA)
		if err != nil {
			fmt.Printf("Unable to get parent commit '%s': err '%v'\n", parent.SHA, err)
			ex.Exit(1)
		}
		// fmt.Printf("pcommit '%+v', len %d\n", pcommit, len(pcommit.Parents))
		if len(pcommit.Parents) == 2 {
			parent = &pcommit.Parents[1]
			pcommit = nil
		} else {
			break
		}
	}
	var commits = make(map[string]*commitsByAuthor)
	apcommit := pcommit
	if *pcommit.Author.Name == *commit.Author.Name {
		pdbg.Pdbgf("Same author '%s', so call checkParentCommits\nInitial message: '%s'", *pcommit.Author.Name, *commit.Message)
		apcommit = &pcommit.Parents[0]
	}
	commits = checkParentCommits(apcommit, *commit.Message)
	if len(commits) == 0 {
		pauthorname := *pcommit.Author.Name
		pcommitsByAuthor := &commitsByAuthor{pcommit.Author, []*github.Commit{pcommit}}
		commits[pauthorname] = pcommitsByAuthor
		pdbg.Pdbgf("Put single commit '%s' for author '%s'", pcommitsByAuthor, pauthorname)
	}
	res := ""
	for _, pcommitsByAuthor := range commits {
		author := pcommitsByAuthor.author
		pcommits := pcommitsByAuthor.pcommits
		plogin := login(*author.Email, *author.Name)
		first := true
		for i, pcommit := range pcommits {
			if first {
				res = "See "
			} else {
				res = res + ", "
			}
			first = false
			if i == len(pcommits)-1 && i > 0 {
				res = res + "and "
			}
			c := fmt.Sprintf("[commit %s](https://github.com/git/git/commit/%s) [%s]",
				(*pcommit.SHA)[:7], *pcommit.SHA, pcommit.Author.Date.Format("02 Jan 2006"))
			res = res + c
		}
		res = res + fmt.Sprintf(" by [%s (`%s`)](https://github.com/%s).  \n",
			*author.Name, plogin, plogin)
		// seec 777e75b60568b613e452ebbb30a1fb27c4fd7d8a, https://github.com/git/git/commit/777e75b60568b613e452ebbb30a1fb27c4fd7d8a
		res = collect(res, *pcommit.Message, "Test-adapted-from")
		// seec 6dec263333417738528089834bd8cda72017aa31, https://github.com/git/git/commit/6dec263333417738528089834bd8cda72017aa31
		// seec 324a9f41cbf96ad994efc3b20be239116eba0dae, https://github.com/git/git/commit/324a9f41cbf96ad994efc3b20be239116eba0dae
		res = collect(res, *pcommit.Message, "Helped-by")
	}
	return res
}

// for cases like commit a6be52e239df4d4a469a5324273f43a0695fe95d
func checkParentCommits(apcommit *github.Commit, commitmsg string) map[string]*commitsByAuthor {
	res := make(map[string]*commitsByAuthor)
	pcommit, _, err := client.Git.GetCommit("git", "git", *apcommit.SHA)
	if err != nil {
		fmt.Printf("Unable to get check parent commit '%s': err '%v'\n", *apcommit.SHA, err)
		ex.Exit(1)
	}
	pdbg.Pdbgf("pcommit %s", *pcommit.SHA)
	pcommitmsgs := strings.Split(*pcommit.Message, "\n")
	title := pcommitmsgs[0]
	pdbg.Pdbgf("title '%s'", title)
	if strings.Contains(commitmsg, title) {
		pauthorname := *pcommit.Author.Name
		pdbg.Pdbgf("pauthorname='%s' for '%v'", pauthorname, pcommit.Author)
		pcommitsByAuthor := res[pauthorname]
		if pcommitsByAuthor == nil {
			pcommitsByAuthor = &commitsByAuthor{pcommit.Author, []*github.Commit{}}
		}
		pcommitsByAuthor.pcommits = append(pcommitsByAuthor.pcommits, pcommit)
		res[pauthorname] = pcommitsByAuthor
		pdbg.Pdbgf("call checkParentCommits with parents '%+v', pca '%s' for '%s'",
			pcommit.Parents, pcommitsByAuthor.String(), pauthorname)
		ppcommits := checkParentCommits(&pcommit.Parents[0], commitmsg)
		for authorName, pcommitsByAuthor := range ppcommits {
			acommitsByAuthor := res[authorName]
			if acommitsByAuthor == nil {
				res[authorName] = pcommitsByAuthor
			} else {
				for _, pc := range pcommitsByAuthor.pcommits {
					acommitsByAuthor.pcommits = append(acommitsByAuthor.pcommits, pc)
				}
				res[authorName] = acommitsByAuthor
				pdbg.Pdbgf("Put commits '%s' for author '%s'", acommitsByAuthor.String(), authorName)
			}
		}
	}
	return res
}

func login(email string, name string) string {
	opts := &github.SearchOptions{Order: "desc"}
	var res *github.UsersSearchResult
	var err error
	if email != "" {
		res, _, err = client.Search.Users(email, opts)
		if err != nil {
			fmt.Printf("Unable to search user '%s': err '%v'", email, err)
			ex.Exit(1)
		}
	}
	if res == nil || *res.Total == 0 {
		name = strings.Replace(name, "-", " ", -1)
		res, _, err = client.Search.Users(name, opts)
		if err != nil {
			fmt.Printf("Unable to search user '%s': err '%v'", name, err)
			ex.Exit(1)
		}
	}
	if res == nil || *res.Total == 0 {
		var resIssues *github.IssuesSearchResult
		issueSearch := fmt.Sprintf(`"Signed-off-by: %s <%s>"`, name, email)
		resIssues, _, err = client.Search.Issues(issueSearch, opts)
		if err != nil {
			fmt.Printf("Unable to search issue '%s': err '%v'", issueSearch, err)
			ex.Exit(1)
		}
		if resIssues == nil || *resIssues.Total == 0 {
			return ""
		}
		issue := resIssues.Issues[0]
		return *issue.User.Login
	}
	if res == nil || *res.Total == 0 {
		return ""
	}
	return *res.Users[0].Login
}

func collect(res, msg, activity string) string {
	re := regexp.MustCompile(fmt.Sprintf(`%s:\s+([^<\r\n]+)\s+<([^>\r\n]+)>`, activity))
	activitymsg := activity + ": "
	first := true
	allresc := re.FindAllStringSubmatch(msg, -1)
	for i, resc := range allresc {
		dot := ""
		if len(resc) != 3 {
			continue
		}
		name := resc[1]
		email := resc[2]
		login := login(email, name)
		if !first {
			activitymsg = activitymsg + ", "
		}
		if i == len(allresc)-1 {
			dot = "."
			if i > 0 {
				activitymsg = activitymsg + "and "
			}
		}
		if login == "" {
			activitymsg = activitymsg + fmt.Sprintf("%s <%s>%s", name, email, dot)
			first = false
			continue
		}
		activitymsg = activitymsg + fmt.Sprintf("[%s (`%s`)](https://github.com/%s)%s", name, login, login, dot)
		first = false
	}
	if !first {
		res = res + activitymsg + "  \n"
	}
	return res
}
<|endoftext|>


<commit_before>// Except.go: Contains functions to make handling panics less PITA

package helpers

import (
	"fmt"
	"reflect"
	"runtime"
	"strconv"

	"math/rand"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/bwmarrin/discordgo"
	"github.com/getsentry/raven-go"
)

// RecoverDiscord recover()s and sends a message to discord
func RecoverDiscord(msg *discordgo.Message) {
	err := recover()
	if err != nil {
		SendError(msg, err)
	}
}

// Recover recover()s and prints the error to console
func Recover() {
	err := recover()
	if err != nil {
		fmt.Printf("%#v\n", err)

		//raven.SetUserContext(&raven.User{})
		raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
	}
}

// SoftRelax is a softer form of Relax()
// Calls a callback instead of panicking
func SoftRelax(err error, cb Callback) {
	if err != nil {
		cb()
	}
}

// Relax is a helper to reduce if-checks if panicking is allowed
// If $err is nil this is a no-op. Panics otherwise.
func Relax(err error) {
	if err != nil {
		if DEBUG_MODE == true {
			fmt.Printf("%#v\n", err)
			panic(err)
			if err, ok := err.(*discordgo.RESTError); ok && err != nil && err.Message != nil {
				fmt.Println(strconv.Itoa(err.Message.Code)+":", err.Message.Message)
			} else {
				fmt.Println(err)
			}
		}
		panic(err)
	}
}

// RelaxEmbed does nothing if $err is nil, prints a notice if there are no permissions to embed, else sends it to Relax()
func RelaxEmbed(err error, channelID string, commandMessageID string) {
	if err != nil {
		if errD, ok := err.(*discordgo.RESTError); ok {
			if errD.Message.Code == 50013 {
				if channelID != "" {
					_, err = cache.GetSession().ChannelMessageSend(channelID, GetText("bot.errors.no-embed"))
					RelaxMessage(err, channelID, commandMessageID)
				}
				return
			}
		}
		Relax(err)
	}
}

// RelaxEmbed does nothing if $err is nil or if there are no permissions to send a message, else sends it to Relax()
func RelaxMessage(err error, channelID string, commandMessageID string) {
	if err != nil {
		if errD, ok := err.(*discordgo.RESTError); ok {
			if errD.Message.Code == 50013 {
				if channelID != "" && commandMessageID != "" {
					reactions := []string{
						":blobstop:317034621953114112",
						":blobweary:317036265071575050",
						":googlespeaknoevil:317036753074651139",
						":notlikeblob:349342777978519562",
					}
					cache.GetSession().MessageReactionAdd(channelID, commandMessageID, reactions[rand.Intn(len(reactions))])
				}
			} else {
				Relax(err)
			}
		} else {
			Relax(err)
		}
	}
}

// RelaxAssertEqual panics if a is not b
func RelaxAssertEqual(a interface{}, b interface{}, err error) {
	if !reflect.DeepEqual(a, b) {
		Relax(err)
	}
}

// RelaxAssertUnequal panics if a is b
func RelaxAssertUnequal(a interface{}, b interface{}, err error) {
	if reflect.DeepEqual(a, b) {
		Relax(err)
	}
}

// SendError Takes an error and sends it to discord and sentry.io
func SendError(msg *discordgo.Message, err interface{}) {
	if DEBUG_MODE == true {
		buf := make([]byte, 1<<16)
		stackSize := runtime.Stack(buf, false)

		cache.GetSession().ChannelMessageSend(
			msg.ChannelID,
			"Error <:blobfrowningbig:317028438693117962>\n```\n"+fmt.Sprintf("%#v\n", err)+fmt.Sprintf("%s\n", string(buf[0:stackSize]))+"\n```",
		)
	} else {
		if errR, ok := err.(*discordgo.RESTError); ok && errR != nil && errR.Message != nil {
			if msg != nil {
				cache.GetSession().ChannelMessageSend(
					msg.ChannelID,
					"Error <:blobfrowningbig:317028438693117962>\n`Sekl#7397` has been notified.\n```\n"+fmt.Sprintf("%#v", errR.Message.Message)+"\n```",
				)
			}
		} else {
			if msg != nil {
				cache.GetSession().ChannelMessageSend(
					msg.ChannelID,
					"Error <:blobfrowningbig:317028438693117962>\n`Sekl#7397` has been notified.\n```\n"+fmt.Sprintf("%#v", err)+"\n```",
				)
			}
		}
	}

	raven.SetUserContext(&raven.User{
		ID:       msg.ID,
		Username: msg.Author.Username + "#" + msg.Author.Discriminator,
	})

	raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{
		"ChannelID":       msg.ChannelID,
		"Content":         msg.Content,
		"Timestamp":       string(msg.Timestamp),
		"TTS":             strconv.FormatBool(msg.Tts),
		"MentionEveryone": strconv.FormatBool(msg.MentionEveryone),
		"IsBot":           strconv.FormatBool(msg.Author.Bot),
	})
}
<commit_msg>[except] removes username from error message<commit_after>// Except.go: Contains functions to make handling panics less PITA

package helpers

import (
	"fmt"
	"reflect"
	"runtime"
	"strconv"

	"math/rand"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/bwmarrin/discordgo"
	"github.com/getsentry/raven-go"
)

// RecoverDiscord recover()s and sends a message to discord
func RecoverDiscord(msg *discordgo.Message) {
	err := recover()
	if err != nil {
		SendError(msg, err)
	}
}

// Recover recover()s and prints the error to console
func Recover() {
	err := recover()
	if err != nil {
		fmt.Printf("%#v\n", err)

		//raven.SetUserContext(&raven.User{})
		raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{})
	}
}

// SoftRelax is a softer form of Relax()
// Calls a callback instead of panicking
func SoftRelax(err error, cb Callback) {
	if err != nil {
		cb()
	}
}

// Relax is a helper to reduce if-checks if panicking is allowed
// If $err is nil this is a no-op. Panics otherwise.
func Relax(err error) {
	if err != nil {
		if DEBUG_MODE == true {
			fmt.Printf("%#v\n", err)
			panic(err)
			if err, ok := err.(*discordgo.RESTError); ok && err != nil && err.Message != nil {
				fmt.Println(strconv.Itoa(err.Message.Code)+":", err.Message.Message)
			} else {
				fmt.Println(err)
			}
		}
		panic(err)
	}
}

// RelaxEmbed does nothing if $err is nil, prints a notice if there are no permissions to embed, else sends it to Relax()
func RelaxEmbed(err error, channelID string, commandMessageID string) {
	if err != nil {
		if errD, ok := err.(*discordgo.RESTError); ok {
			if errD.Message.Code == 50013 {
				if channelID != "" {
					_, err = cache.GetSession().ChannelMessageSend(channelID, GetText("bot.errors.no-embed"))
					RelaxMessage(err, channelID, commandMessageID)
				}
				return
			}
		}
		Relax(err)
	}
}

// RelaxEmbed does nothing if $err is nil or if there are no permissions to send a message, else sends it to Relax()
func RelaxMessage(err error, channelID string, commandMessageID string) {
	if err != nil {
		if errD, ok := err.(*discordgo.RESTError); ok {
			if errD.Message.Code == 50013 {
				if channelID != "" && commandMessageID != "" {
					reactions := []string{
						":blobstop:317034621953114112",
						":blobweary:317036265071575050",
						":googlespeaknoevil:317036753074651139",
						":notlikeblob:349342777978519562",
					}
					cache.GetSession().MessageReactionAdd(channelID, commandMessageID, reactions[rand.Intn(len(reactions))])
				}
			} else {
				Relax(err)
			}
		} else {
			Relax(err)
		}
	}
}

// RelaxAssertEqual panics if a is not b
func RelaxAssertEqual(a interface{}, b interface{}, err error) {
	if !reflect.DeepEqual(a, b) {
		Relax(err)
	}
}

// RelaxAssertUnequal panics if a is b
func RelaxAssertUnequal(a interface{}, b interface{}, err error) {
	if reflect.DeepEqual(a, b) {
		Relax(err)
	}
}

// SendError Takes an error and sends it to discord and sentry.io
func SendError(msg *discordgo.Message, err interface{}) {
	if DEBUG_MODE == true {
		buf := make([]byte, 1<<16)
		stackSize := runtime.Stack(buf, false)

		cache.GetSession().ChannelMessageSend(
			msg.ChannelID,
			"Error <:blobfrowningbig:317028438693117962>\n```\n"+fmt.Sprintf("%#v\n", err)+fmt.Sprintf("%s\n", string(buf[0:stackSize]))+"\n```",
		)
	} else {
		if errR, ok := err.(*discordgo.RESTError); ok && errR != nil && errR.Message != nil {
			if msg != nil {
				cache.GetSession().ChannelMessageSend(
					msg.ChannelID,
					"Error <:blobfrowningbig:317028438693117962>\n```\n"+fmt.Sprintf("%#v", errR.Message.Message)+"\n```",
				)
			}
		} else {
			if msg != nil {
				cache.GetSession().ChannelMessageSend(
					msg.ChannelID,
					"Error <:blobfrowningbig:317028438693117962>\n```\n"+fmt.Sprintf("%#v", err)+"\n```",
				)
			}
		}
	}

	raven.SetUserContext(&raven.User{
		ID:       msg.ID,
		Username: msg.Author.Username + "#" + msg.Author.Discriminator,
	})

	raven.CaptureError(fmt.Errorf("%#v", err), map[string]string{
		"ChannelID":       msg.ChannelID,
		"Content":         msg.Content,
		"Timestamp":       string(msg.Timestamp),
		"TTS":             strconv.FormatBool(msg.Tts),
		"MentionEveryone": strconv.FormatBool(msg.MentionEveryone),
		"IsBot":           strconv.FormatBool(msg.Author.Bot),
	})
}
<|endoftext|>


<commit_before>package goparsify

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestParsify(t *testing.T) {
	result := Result{}
	t.Run("strings", func(t *testing.T) {
		Parsify("ff")(NewState("ffooo"), &result)
		require.Equal(t, "ff", result.Token)
	})

	t.Run("parsers", func(t *testing.T) {
		Parsify(Chars("f"))(NewState("ffooo"), &result)
		require.Equal(t, "ff", result.Token)
	})

	t.Run("parser funcs", func(t *testing.T) {
		Parsify(func(p *State, node *Result) { node.Token = "hello" })(NewState("ffooo"), &result)

		require.Equal(t, "hello", result.Token)
	})

	t.Run("*parsers", func(t *testing.T) {
		var parser Parser
		parserfied := Parsify(&parser)
		parser = Chars("f")

		parserfied(NewState("ffooo"), &result)
		require.Equal(t, "ff", result.Token)
	})

	require.Panics(t, func() {
		Parsify(1)
	})
}

func TestParsifyAll(t *testing.T) {
	parsers := ParsifyAll("ff", "gg")

	result := Result{}
	parsers[0](NewState("ffooo"), &result)
	require.Equal(t, "ff", result.Token)

	result = Result{}
	parsers[1](NewState("ffooo"), &result)
	require.Equal(t, "", result.Token)
}

func TestExact(t *testing.T) {
	t.Run("success string", func(t *testing.T) {
		node, ps := runParser("foobar", Exact("fo"))
		require.Equal(t, "fo", node.Token)
		require.Equal(t, "obar", ps.Get())
	})

	t.Run("success char", func(t *testing.T) {
		node, ps := runParser("foobar", Exact("f"))
		require.Equal(t, "f", node.Token)
		require.Equal(t, "oobar", ps.Get())
	})

	t.Run("error", func(t *testing.T) {
		_, ps := runParser("foobar", Exact("bar"))
		require.Equal(t, "bar", ps.Error.expected)
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("error char", func(t *testing.T) {
		_, ps := runParser("foobar", Exact("o"))
		require.Equal(t, "o", ps.Error.expected)
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("eof char", func(t *testing.T) {
		_, ps := runParser("", Exact("o"))
		require.Equal(t, "o", ps.Error.expected)
		require.Equal(t, 0, ps.Pos)
	})
}

func TestChars(t *testing.T) {
	t.Run("full match", func(t *testing.T) {
		node, ps := runParser("foobar", Chars("a-z"))
		require.Equal(t, "foobar", node.Token)
		require.Equal(t, "", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("partial match", func(t *testing.T) {
		node, ps := runParser("a1b2c3d4efg", Chars("1-4d-a"))
		require.Equal(t, "a1b2c3d4", node.Token)
		require.Equal(t, "efg", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("limited match", func(t *testing.T) {
		node, ps := runParser("a1b2c3d4efg", Chars("1-4d-a", 1, 2))
		require.Equal(t, "a1", node.Token)
		require.Equal(t, "b2c3d4efg", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("escaped hyphen", func(t *testing.T) {
		node, ps := runParser(`ab-ab\cde`, Chars(`a\-b`))
		require.Equal(t, "ab-ab", node.Token)
		require.Equal(t, `\cde`, ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("no match", func(t *testing.T) {
		_, ps := runParser("ffffff", Chars("0-9"))
		require.Equal(t, "offset 0: expected 0-9", ps.Error.Error())
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("no match with min", func(t *testing.T) {
		_, ps := runParser("ffffff", Chars("0-9", 4))
		require.Equal(t, "0-9", ps.Error.expected)
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("test exact matches", func(t *testing.T) {
		node, ps := runParser("aaff", Chars("abcd"))
		require.Equal(t, "aa", node.Token)
		require.Equal(t, 2, ps.Pos)
		require.False(t, ps.Errored())
	})

	t.Run("test not matches", func(t *testing.T) {
		node, ps := runParser("aaff", NotChars("ff"))
		require.Equal(t, "aa", node.Token)
		require.Equal(t, 2, ps.Pos)
		require.False(t, ps.Errored())
	})

	require.Panics(t, func() {
		Chars("a-b", 1, 2, 3)
	})
}

func TestRegex(t *testing.T) {
	t.Run("full match", func(t *testing.T) {
		node, ps := runParser("hello", Regex("[a-z]*"))
		require.Equal(t, "hello", node.Token)
		require.Equal(t, "", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("limited match", func(t *testing.T) {
		node, ps := runParser("hello world", Regex("[a-z]*"))
		require.Equal(t, "hello", node.Token)
		require.Equal(t, " world", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("no match", func(t *testing.T) {
		_, ps := runParser("1234", Regex("[a-z]*"))
		require.Equal(t, "offset 0: expected [a-z]*", ps.Error.Error())
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("eof", func(t *testing.T) {
		_, ps := runParser("", Regex("[a-z]*"))
		require.Equal(t, "offset 0: expected [a-z]*", ps.Error.Error())
		require.Equal(t, 0, ps.Pos)
	})
}

func TestParseString(t *testing.T) {
	Y := Map("hello", func(n *Result) { n.Result = n.Token })

	t.Run("full match", func(t *testing.T) {
		result, err := Run(Y, "hello")
		require.Equal(t, "hello", result)
		require.NoError(t, err)
	})

	t.Run("partial match", func(t *testing.T) {
		result, err := Run(Y, "hello world")
		require.Equal(t, "hello", result)
		require.Error(t, err)
		require.Equal(t, "left unparsed: world", err.Error())
	})

	t.Run("error", func(t *testing.T) {
		result, err := Run(Y, "world")
		require.Nil(t, result)
		require.Error(t, err)
		require.Equal(t, "offset 0: expected hello", err.Error())
	})
}

func TestAutoWS(t *testing.T) {
	t.Run("ws is not automatically consumed", func(t *testing.T) {
		_, ps := runParser(" hello", NoAutoWS("hello"))
		require.Equal(t, "offset 0: expected hello", ps.Error.Error())
	})

	t.Run("ws is can be explicitly consumed ", func(t *testing.T) {
		result, ps := runParser(" hello", NoAutoWS(Seq(ASCIIWhitespace, "hello")))
		require.Equal(t, "hello", result.Child[1].Token)
		require.Equal(t, "", ps.Get())
	})

	t.Run("unicode whitespace", func(t *testing.T) {
		result, ps := runParser(" \u202f hello", NoAutoWS(Seq(UnicodeWhitespace, "hello")))
		require.Equal(t, "hello", result.Child[1].Token)
		require.Equal(t, "", ps.Get())
		require.False(t, ps.Errored())
	})
}

func TestUntil(t *testing.T) {
	parser := Until("world", ".")

	t.Run("success", func(t *testing.T) {
		result, ps := runParser("this is the end of the world", parser)
		require.Equal(t, "this is the end of the ", result.Token)
		require.Equal(t, "world", ps.Get())
	})

	t.Run("eof", func(t *testing.T) {
		result, ps := runParser("this is the end of it all", parser)
		require.Equal(t, "this is the end of it all", result.Token)
		require.Equal(t, "", ps.Get())
	})
}

func runParser(input string, parser Parser) (Result, *State) {
	ps := NewState(input)
	result := Result{}
	parser(ps, &result)
	return result, ps
}
<commit_msg>Add failing test for chars with a hyphen<commit_after>package goparsify

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestParsify(t *testing.T) {
	result := Result{}
	t.Run("strings", func(t *testing.T) {
		Parsify("ff")(NewState("ffooo"), &result)
		require.Equal(t, "ff", result.Token)
	})

	t.Run("parsers", func(t *testing.T) {
		Parsify(Chars("f"))(NewState("ffooo"), &result)
		require.Equal(t, "ff", result.Token)
	})

	t.Run("parser funcs", func(t *testing.T) {
		Parsify(func(p *State, node *Result) { node.Token = "hello" })(NewState("ffooo"), &result)

		require.Equal(t, "hello", result.Token)
	})

	t.Run("*parsers", func(t *testing.T) {
		var parser Parser
		parserfied := Parsify(&parser)
		parser = Chars("f")

		parserfied(NewState("ffooo"), &result)
		require.Equal(t, "ff", result.Token)
	})

	require.Panics(t, func() {
		Parsify(1)
	})
}

func TestParsifyAll(t *testing.T) {
	parsers := ParsifyAll("ff", "gg")

	result := Result{}
	parsers[0](NewState("ffooo"), &result)
	require.Equal(t, "ff", result.Token)

	result = Result{}
	parsers[1](NewState("ffooo"), &result)
	require.Equal(t, "", result.Token)
}

func TestExact(t *testing.T) {
	t.Run("success string", func(t *testing.T) {
		node, ps := runParser("foobar", Exact("fo"))
		require.Equal(t, "fo", node.Token)
		require.Equal(t, "obar", ps.Get())
	})

	t.Run("success char", func(t *testing.T) {
		node, ps := runParser("foobar", Exact("f"))
		require.Equal(t, "f", node.Token)
		require.Equal(t, "oobar", ps.Get())
	})

	t.Run("error", func(t *testing.T) {
		_, ps := runParser("foobar", Exact("bar"))
		require.Equal(t, "bar", ps.Error.expected)
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("error char", func(t *testing.T) {
		_, ps := runParser("foobar", Exact("o"))
		require.Equal(t, "o", ps.Error.expected)
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("eof char", func(t *testing.T) {
		_, ps := runParser("", Exact("o"))
		require.Equal(t, "o", ps.Error.expected)
		require.Equal(t, 0, ps.Pos)
	})
}

func TestChars(t *testing.T) {
	t.Run("full match", func(t *testing.T) {
		node, ps := runParser("foobar", Chars("a-z"))
		require.Equal(t, "foobar", node.Token)
		require.Equal(t, "", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("partial match", func(t *testing.T) {
		node, ps := runParser("a1b2c3d4efg", Chars("1-4d-a"))
		require.Equal(t, "a1b2c3d4", node.Token)
		require.Equal(t, "efg", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("limited match", func(t *testing.T) {
		node, ps := runParser("a1b2c3d4efg", Chars("1-4d-a", 1, 2))
		require.Equal(t, "a1", node.Token)
		require.Equal(t, "b2c3d4efg", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("escaped hyphen", func(t *testing.T) {
		node, ps := runParser(`ab-ab\cde`, Chars(`a\-b`))
		require.Equal(t, "ab-ab", node.Token)
		require.Equal(t, `\cde`, ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("unescaped hyphen", func(t *testing.T) {
		node, ps := runParser("19-", Chars("0-9"))
		require.Equal(t, "19", node.Token)
		require.Equal(t, 2, ps.Pos)
		require.False(t, ps.Errored())
	})

	t.Run("no match", func(t *testing.T) {
		_, ps := runParser("ffffff", Chars("0-9"))
		require.Equal(t, "offset 0: expected 0-9", ps.Error.Error())
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("no match with min", func(t *testing.T) {
		_, ps := runParser("ffffff", Chars("0-9", 4))
		require.Equal(t, "0-9", ps.Error.expected)
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("test exact matches", func(t *testing.T) {
		node, ps := runParser("aaff", Chars("abcd"))
		require.Equal(t, "aa", node.Token)
		require.Equal(t, 2, ps.Pos)
		require.False(t, ps.Errored())
	})

	t.Run("test not matches", func(t *testing.T) {
		node, ps := runParser("aaff", NotChars("ff"))
		require.Equal(t, "aa", node.Token)
		require.Equal(t, 2, ps.Pos)
		require.False(t, ps.Errored())
	})

	require.Panics(t, func() {
		Chars("a-b", 1, 2, 3)
	})
}

func TestRegex(t *testing.T) {
	t.Run("full match", func(t *testing.T) {
		node, ps := runParser("hello", Regex("[a-z]*"))
		require.Equal(t, "hello", node.Token)
		require.Equal(t, "", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("limited match", func(t *testing.T) {
		node, ps := runParser("hello world", Regex("[a-z]*"))
		require.Equal(t, "hello", node.Token)
		require.Equal(t, " world", ps.Get())
		require.False(t, ps.Errored())
	})

	t.Run("no match", func(t *testing.T) {
		_, ps := runParser("1234", Regex("[a-z]*"))
		require.Equal(t, "offset 0: expected [a-z]*", ps.Error.Error())
		require.Equal(t, 0, ps.Pos)
	})

	t.Run("eof", func(t *testing.T) {
		_, ps := runParser("", Regex("[a-z]*"))
		require.Equal(t, "offset 0: expected [a-z]*", ps.Error.Error())
		require.Equal(t, 0, ps.Pos)
	})
}

func TestParseString(t *testing.T) {
	Y := Map("hello", func(n *Result) { n.Result = n.Token })

	t.Run("full match", func(t *testing.T) {
		result, err := Run(Y, "hello")
		require.Equal(t, "hello", result)
		require.NoError(t, err)
	})

	t.Run("partial match", func(t *testing.T) {
		result, err := Run(Y, "hello world")
		require.Equal(t, "hello", result)
		require.Error(t, err)
		require.Equal(t, "left unparsed: world", err.Error())
	})

	t.Run("error", func(t *testing.T) {
		result, err := Run(Y, "world")
		require.Nil(t, result)
		require.Error(t, err)
		require.Equal(t, "offset 0: expected hello", err.Error())
	})
}

func TestAutoWS(t *testing.T) {
	t.Run("ws is not automatically consumed", func(t *testing.T) {
		_, ps := runParser(" hello", NoAutoWS("hello"))
		require.Equal(t, "offset 0: expected hello", ps.Error.Error())
	})

	t.Run("ws is can be explicitly consumed ", func(t *testing.T) {
		result, ps := runParser(" hello", NoAutoWS(Seq(ASCIIWhitespace, "hello")))
		require.Equal(t, "hello", result.Child[1].Token)
		require.Equal(t, "", ps.Get())
	})

	t.Run("unicode whitespace", func(t *testing.T) {
		result, ps := runParser(" \u202f hello", NoAutoWS(Seq(UnicodeWhitespace, "hello")))
		require.Equal(t, "hello", result.Child[1].Token)
		require.Equal(t, "", ps.Get())
		require.False(t, ps.Errored())
	})
}

func TestUntil(t *testing.T) {
	parser := Until("world", ".")

	t.Run("success", func(t *testing.T) {
		result, ps := runParser("this is the end of the world", parser)
		require.Equal(t, "this is the end of the ", result.Token)
		require.Equal(t, "world", ps.Get())
	})

	t.Run("eof", func(t *testing.T) {
		result, ps := runParser("this is the end of it all", parser)
		require.Equal(t, "this is the end of it all", result.Token)
		require.Equal(t, "", ps.Get())
	})
}

func runParser(input string, parser Parser) (Result, *State) {
	ps := NewState(input)
	result := Result{}
	parser(ps, &result)
	return result, ps
}
<|endoftext|>


<commit_before>// Copyright 2012 Michael Meier. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// Package sers offers serial port access. It is a stated goal of this
// package to allow for non-standard bit rates as the may be useful
// in a wide range of embedded projects.
package sers

import "C"

import (
	"fmt"
	"io"
)

const (
	N = 0 // no parity
	E = 1 // even parity
	O = 2 // odd parity
)

const (
	NO_HANDSHAKE     = 0
	RTSCTS_HANDSHAKE = 1
)

// Serialport represents a serial port and offers configuration of baud
// rate, frame format, handshaking and read paramters.
type SerialPort interface {
	io.Reader
	io.Writer
	io.Closer

	// SetMode sets the frame format and handshaking configuration.
	// baudrate may be freely chosen, the driver is allowed to reject
	// unachievable baud rates. databits may be any number of data bits
	// supported by the driver. parity is one of (N|O|E) for none, odd
	// or even parity. handshake is either NO_HANDSHAKE or
	// RTSCTS_HANDSHAKE.
	SetMode(baudrate, databits, parity, stopbits, handshake int) error

	// SetReadParams sets the minimum number of bits to read and a read
	// timeout in seconds. These parameters roughly correspond to the
	// UNIX termios concepts of VMIN and VTIME.
	SetReadParams(minread int, timeout float64) error
}

type StringError string

func (se StringError) Error() string {
	return string(se)
}

type ParameterError struct {
	Parameter string
	Reason    string
}

func (pe *ParameterError) Error() string {
	return fmt.Sprintf("error in parameter '%s': %s")
}

type Error struct {
	Operation       string
	UnderlyingError error
}

func (e *Error) Error() string {
	return fmt.Sprintf("%s: %v", e.Operation, e.UnderlyingError)
}
<commit_msg>remove 'import "C"' from sers.go<commit_after>// Copyright 2012 Michael Meier. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// Package sers offers serial port access. It is a stated goal of this
// package to allow for non-standard bit rates as the may be useful
// in a wide range of embedded projects.
package sers

import (
	"fmt"
	"io"
)

const (
	N = 0 // no parity
	E = 1 // even parity
	O = 2 // odd parity
)

const (
	NO_HANDSHAKE     = 0
	RTSCTS_HANDSHAKE = 1
)

// Serialport represents a serial port and offers configuration of baud
// rate, frame format, handshaking and read paramters.
type SerialPort interface {
	io.Reader
	io.Writer
	io.Closer

	// SetMode sets the frame format and handshaking configuration.
	// baudrate may be freely chosen, the driver is allowed to reject
	// unachievable baud rates. databits may be any number of data bits
	// supported by the driver. parity is one of (N|O|E) for none, odd
	// or even parity. handshake is either NO_HANDSHAKE or
	// RTSCTS_HANDSHAKE.
	SetMode(baudrate, databits, parity, stopbits, handshake int) error

	// SetReadParams sets the minimum number of bits to read and a read
	// timeout in seconds. These parameters roughly correspond to the
	// UNIX termios concepts of VMIN and VTIME.
	SetReadParams(minread int, timeout float64) error
}

type StringError string

func (se StringError) Error() string {
	return string(se)
}

type ParameterError struct {
	Parameter string
	Reason    string
}

func (pe *ParameterError) Error() string {
	return fmt.Sprintf("error in parameter '%s': %s")
}

type Error struct {
	Operation       string
	UnderlyingError error
}

func (e *Error) Error() string {
	return fmt.Sprintf("%s: %v", e.Operation, e.UnderlyingError)
}
<|endoftext|>


<commit_before>package main

import (
	_ "time"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"github.com/gorilla/sessions"
	"io"
	"net/http"
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
	"runtime"
	"errors"
	"html/template"
)

var templates = template.Must(template.ParseFiles("index.html"))

const MESSAGE_QUEUE_SIZE = 10

const STATUS_FAILURE = "{\"status\":\"failure\"}"

var authKey = []byte("somesecretauth")
var store sessions.Store

var pool *Pool
var clients map[int64]*Client

var db *sql.DB

type Pool struct {
	in  chan *Client
	out chan *Room
}

type Client struct {
	id      int64
	in      chan string
	out     chan string
	retChan chan *Room
}

type Room struct {
	id      []byte
	client1 *Client
	client2 *Client
}

func (p *Pool) Pair() {
	for {
		c1, c2 := <-p.in, <-p.in

		for c1.id == c2.id {
			c2 = <- p.in
		}

		fmt.Println("match found for ", c1.id, " and ", c2.id)

		b := make([]byte, 32)
		n, err := io.ReadFull(rand.Reader, b)
		if err != nil || n != 32 {
			return
		}

		room := &Room{b, c1, c2}

		c1.in, c2.in = c2.out, c1.out

		c1.retChan <- room
		c2.retChan <- room
	}
}

func newPool() *Pool {
	pool := &Pool{
		in:  make(chan *Client),
		out: make(chan *Room),
	}

	go pool.Pair()

	return pool
}

func UIDFromSession(w http.ResponseWriter, r *http.Request) (int64, error) {
	session, _ := store.Get(r, "session")
	userid := session.Values["userid"]

	if userid == nil {
		return 0, errors.New("no cookie set")
	}
	return userid.(int64), nil
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	db, _ = sql.Open("mysql", "root:pass@/suitup")
	defer db.Close()

	store = sessions.NewCookieStore(authKey)

	pool = newPool()
	clients = make(map[int64]*Client)

	http.HandleFunc("/", mainHandle)

	http.HandleFunc("/login", login)

	http.HandleFunc("/message/check", checkMessage)
	http.HandleFunc("/message/send", sendMessage)

	http.HandleFunc("/question/new", newQuestion)

	http.HandleFunc("/chatroom/join", joinChatRoom)
	http.HandleFunc("/chatroom/leave", leaveChatRoom)

	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("/home/suitup/hackmit/assets/"))))
	http.ListenAndServe(":8080", nil)
}

func mainHandle(w http.ResponseWriter, r *http.Request) {
	templates.ExecuteTemplate(w, "index.html", nil)
}

func joinChatRoom(w http.ResponseWriter, r *http.Request) {
	uid, err := UIDFromSession(w, r)
	handleError(err)

	fmt.Println("join ", uid)

	retChan := make(chan *Room)
	client := &Client{
		id:      uid,
		in:      nil,
		out:     make(chan string, MESSAGE_QUEUE_SIZE),
		retChan: retChan,
	}
	clients[uid] = client
	pool.in <- client

	chatroom := <- retChan

	fmt.Println("joinChatRoom-chatroom.id: ", chatroom.id)

	fmt.Fprint(w, "{\"status\":\"success\",\"crid\":\"", asciify(chatroom.id), "\"}")
}

func asciify(ba []byte) string {
	ret := make([]byte, len(ba))
	for i, b := range ba {
		ret[i] = (b % 26) + 97
	}
	return string(ret)
}

func leaveChatRoom(w http.ResponseWriter, r *http.Request) {
	uid, _ := UIDFromSession(w, r)
	client := clients[uid]

	fmt.Println("leave ", uid)

	close (client.out)

	client.in = nil
	client.out = make(chan string)
	pool.in <- client

	fmt.Fprint(w, "{\"status\":\"success\"}")
}

func sendMessage(w http.ResponseWriter, r *http.Request) {
	uid, err := UIDFromSession(w, r)
	handleError(err)

	fmt.Println("send ", uid)

	message := r.FormValue("s")

	// message := r.PostFormValue("message")

	client := clients[uid]

	if client != nil && client.out != nil {
		client.out <- message
		fmt.Fprint(w, "{\"status\":\"success\"}")
	} else {
		fmt.Fprint(w, STATUS_FAILURE)
	}
}

func checkMessage(w http.ResponseWriter, r *http.Request) {
	uid, err := UIDFromSession(w, r)
	handleError(err)

	fmt.Println("check ", uid)

	client := clients[uid]

	if client != nil {
		select {
		case message, ok := <- client.in:
			// fmt.Println("message pulled from channel")
			if ok {
				fmt.Fprint(w, message)
			} else {
				client.out = nil
				fmt.Fprint(w, "{\"status\":\"failure\"}")
			}
		default:
			fmt.Fprint(w, "")
		}

	} else {
		fmt.Fprint(w, "")
	}
}

type Question struct {
	Id 	int64			`json:"id"`
	Title string		`json:"title"`
	Body string			`json:"body"`
	Difficulty int 		`json:"diff"`
}

type User struct {
    Id            	int64      	`json:"id"`
    FacebookId  	string		`json:fbid"`
    Username 		string		`json:username"`
    Email 			string		`json:email"`
    Level 			int64		`json:lvl"`
    Score 			int64		`json:score"`
}

func newQuestion(w http.ResponseWriter, r *http.Request) {
	row := db.QueryRow("SELECT * FROM questions ORDER BY RAND()")
	q := new(Question)
	err := row.Scan(&q.Id, &q.Title, &q.Body, &q.Difficulty)

	if err != nil {
		fmt.Println(err)
		fmt.Fprint(w, STATUS_FAILURE)
	}

	b, err := json.Marshal(q)
	if err != nil {
		fmt.Println(err)
		fmt.Fprint(w, STATUS_FAILURE)
	}

	fmt.Fprint(w, string(b))
}

func login(w http.ResponseWriter, r *http.Request) {
	inputToken := r.FormValue("access_token")
	if len(inputToken) != 0 {
		uid := GetMe(inputToken)

		// row := db.QueryRow("SELECT id FROM users")
		row := db.QueryRow("SELECT id FROM users WHERE facebook_id=?", string(uid))
		user := new(User)
		err := row.Scan(&user.Id)

		if err != nil {
			_, err = db.Exec("INSERT INTO users (facebook_id, username, email, level, points) VALUES (?, ?, ?, 0, 0)", uid, "", "")
			if err != nil {
				fmt.Fprint(w, STATUS_FAILURE)
				return
			} else {
				row = db.QueryRow("SELECT id FROM users WHERE facebook_id=?", string(uid))
				err = row.Scan(&user.Id)
				if err != nil {
					fmt.Fprint(w, STATUS_FAILURE)
					return
				}
			}

		}

		session, _ := store.Get(r, "session")
		session.Values["userid"] = user.Id
		session.Save(r, w)

		fmt.Fprint(w, "{\"status\":\"success\"}")
	}
}

func readHttpBody(response *http.Response) string {
	bodyBuffer := make([]byte, 1000)
	var str string

	count, err := response.Body.Read(bodyBuffer)

	for ; count > 0; count, err = response.Body.Read(bodyBuffer) {

		if err != nil {

		}

		str += string(bodyBuffer[:count])
	}

	return str

}

func getUncachedResponse(uri string) (*http.Response, error) {
	request, err := http.NewRequest("GET", uri, nil)

	if err == nil {
		request.Header.Add("Cache-Control", "no-cache")

		client := new(http.Client)

		return client.Do(request)
	}

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

}

func GetMe(token string) string {
	response, err := getUncachedResponse("https://graph.facebook.com/me?access_token="+token)

	if err == nil {

		var jsonBlob interface{}

		responseBody := readHttpBody(response)

		if responseBody != "" {
			err = json.Unmarshal([]byte(responseBody), &jsonBlob)

			if err == nil {
				jsonObj := jsonBlob.(map[string]interface{})
				return jsonObj["id"].(string)
			}
		}
		return err.Error()
	}

	return err.Error()
}

func handleError(err error) {
	if err != nil {
		fmt.Println(err)
	}
}
<commit_msg>join close<commit_after>package main

import (
	_ "time"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"github.com/gorilla/sessions"
	"io"
	"net/http"
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
	"runtime"
	"errors"
	"html/template"
)

var templates = template.Must(template.ParseFiles("index.html"))

const MESSAGE_QUEUE_SIZE = 10

const STATUS_FAILURE = "{\"status\":\"failure\"}"

var authKey = []byte("somesecretauth")
var store sessions.Store

var pool *Pool
var clients map[int64]*Client

var db *sql.DB

type Pool struct {
	in  chan *Client
	out chan *Room
}

type Client struct {
	id      int64
	in      chan string
	out     chan string
	retChan chan *Room
}

type Room struct {
	id      []byte
	client1 *Client
	client2 *Client
}

func (p *Pool) Pair() {
	for {
		c1, c2 := <-p.in, <-p.in

		for c1.id == c2.id {
			c2 = <- p.in
		}

		fmt.Println("match found for ", c1.id, " and ", c2.id)

		b := make([]byte, 32)
		n, err := io.ReadFull(rand.Reader, b)
		if err != nil || n != 32 {
			return
		}

		room := &Room{b, c1, c2}

		c1.in, c2.in = c2.out, c1.out

		c1.retChan <- room
		c2.retChan <- room
	}
}

func newPool() *Pool {
	pool := &Pool{
		in:  make(chan *Client),
		out: make(chan *Room),
	}

	go pool.Pair()

	return pool
}

func UIDFromSession(w http.ResponseWriter, r *http.Request) (int64, error) {
	session, _ := store.Get(r, "session")
	userid := session.Values["userid"]

	if userid == nil {
		return 0, errors.New("no cookie set")
	}
	return userid.(int64), nil
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	db, _ = sql.Open("mysql", "root:pass@/suitup")
	defer db.Close()

	store = sessions.NewCookieStore(authKey)

	pool = newPool()
	clients = make(map[int64]*Client)

	http.HandleFunc("/", mainHandle)

	http.HandleFunc("/login", login)

	http.HandleFunc("/message/check", checkMessage)
	http.HandleFunc("/message/send", sendMessage)

	http.HandleFunc("/question/new", newQuestion)

	http.HandleFunc("/chatroom/join", joinChatRoom)
	http.HandleFunc("/chatroom/leave", leaveChatRoom)

	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("/home/suitup/hackmit/assets/"))))
	http.ListenAndServe(":8080", nil)
}

func mainHandle(w http.ResponseWriter, r *http.Request) {
	templates.ExecuteTemplate(w, "index.html", nil)
}

func joinChatRoom(w http.ResponseWriter, r *http.Request) {
	uid, err := UIDFromSession(w, r)
	handleError(err)

	prev := clients[uid]
	if (prev != nil) {
		close(prev.out)
		delete(clients, uid)
	}

	fmt.Println("join ", uid)

	retChan := make(chan *Room)
	client := &Client{
		id:      uid,
		in:      nil,
		out:     make(chan string, MESSAGE_QUEUE_SIZE),
		retChan: retChan,
	}
	clients[uid] = client
	pool.in <- client

	chatroom := <- retChan

	fmt.Println("joinChatRoom-chatroom.id: ", chatroom.id)

	fmt.Fprint(w, "{\"status\":\"success\",\"crid\":\"", asciify(chatroom.id), "\"}")
}

func asciify(ba []byte) string {
	ret := make([]byte, len(ba))
	for i, b := range ba {
		ret[i] = (b % 26) + 97
	}
	return string(ret)
}

func leaveChatRoom(w http.ResponseWriter, r *http.Request) {
	uid, _ := UIDFromSession(w, r)
	client := clients[uid]

	fmt.Println("leave ", uid)

	close(client.out)

	client.in = nil
	client.out = make(chan string)
	pool.in <- client

	fmt.Fprint(w, "{\"status\":\"success\"}")
}

func sendMessage(w http.ResponseWriter, r *http.Request) {
	uid, err := UIDFromSession(w, r)
	handleError(err)

	fmt.Println("send ", uid)

	message := r.FormValue("s")

	// message := r.PostFormValue("message")

	client := clients[uid]

	if client != nil && client.out != nil {
		client.out <- message
		fmt.Fprint(w, "{\"status\":\"success\"}")
	} else {
		fmt.Fprint(w, STATUS_FAILURE)
	}
}

func checkMessage(w http.ResponseWriter, r *http.Request) {
	uid, err := UIDFromSession(w, r)
	handleError(err)

	fmt.Println("check ", uid)

	client := clients[uid]

	if client != nil {
		select {
		case message, ok := <- client.in:
			if ok {
				fmt.Fprint(w, message)
			} else {
				fmt.Println("channel closed")
				client.out = nil
				fmt.Fprint(w, "{\"status\":\"failure\"}")
			}
		default:
			fmt.Fprint(w, "")
		}

	} else {
		fmt.Fprint(w, "")
	}
}

type Question struct {
	Id 	int64			`json:"id"`
	Title string		`json:"title"`
	Body string			`json:"body"`
	Difficulty int 		`json:"diff"`
}

type User struct {
    Id            	int64      	`json:"id"`
    FacebookId  	string		`json:fbid"`
    Username 		string		`json:username"`
    Email 			string		`json:email"`
    Level 			int64		`json:lvl"`
    Score 			int64		`json:score"`
}

func newQuestion(w http.ResponseWriter, r *http.Request) {
	row := db.QueryRow("SELECT * FROM questions ORDER BY RAND()")
	q := new(Question)
	err := row.Scan(&q.Id, &q.Title, &q.Body, &q.Difficulty)

	if err != nil {
		fmt.Println(err)
		fmt.Fprint(w, STATUS_FAILURE)
	}

	b, err := json.Marshal(q)
	if err != nil {
		fmt.Println(err)
		fmt.Fprint(w, STATUS_FAILURE)
	}

	fmt.Fprint(w, string(b))
}

func login(w http.ResponseWriter, r *http.Request) {
	inputToken := r.FormValue("access_token")
	if len(inputToken) != 0 {
		uid := GetMe(inputToken)

		// row := db.QueryRow("SELECT id FROM users")
		row := db.QueryRow("SELECT id FROM users WHERE facebook_id=?", string(uid))
		user := new(User)
		err := row.Scan(&user.Id)

		if err != nil {
			_, err = db.Exec("INSERT INTO users (facebook_id, username, email, level, points) VALUES (?, ?, ?, 0, 0)", uid, "", "")
			if err != nil {
				fmt.Fprint(w, STATUS_FAILURE)
				return
			} else {
				row = db.QueryRow("SELECT id FROM users WHERE facebook_id=?", string(uid))
				err = row.Scan(&user.Id)
				if err != nil {
					fmt.Fprint(w, STATUS_FAILURE)
					return
				}
			}

		}

		session, _ := store.Get(r, "session")
		session.Values["userid"] = user.Id
		session.Save(r, w)

		fmt.Fprint(w, "{\"status\":\"success\"}")
	}
}

func readHttpBody(response *http.Response) string {
	bodyBuffer := make([]byte, 1000)
	var str string

	count, err := response.Body.Read(bodyBuffer)

	for ; count > 0; count, err = response.Body.Read(bodyBuffer) {

		if err != nil {

		}

		str += string(bodyBuffer[:count])
	}

	return str

}

func getUncachedResponse(uri string) (*http.Response, error) {
	request, err := http.NewRequest("GET", uri, nil)

	if err == nil {
		request.Header.Add("Cache-Control", "no-cache")

		client := new(http.Client)

		return client.Do(request)
	}

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

}

func GetMe(token string) string {
	response, err := getUncachedResponse("https://graph.facebook.com/me?access_token="+token)

	if err == nil {

		var jsonBlob interface{}

		responseBody := readHttpBody(response)

		if responseBody != "" {
			err = json.Unmarshal([]byte(responseBody), &jsonBlob)

			if err == nil {
				jsonObj := jsonBlob.(map[string]interface{})
				return jsonObj["id"].(string)
			}
		}
		return err.Error()
	}

	return err.Error()
}

func handleError(err error) {
	if err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package samples

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"sync"

	"github.com/jacobsa/ogletest"
	"golang.org/x/net/context"
)

var fToolPath = flag.String(
	"mount_sample",
	"",
	"Path to the mount_sample tool. If unset, we will compile it.")

// A struct that implements common behavior needed by tests in the samples/
// directory where the file system is mounted by a subprocess. Use it as an
// embedded field in your test fixture, calling its SetUp method from your
// SetUp method after setting the MountType and MountFlags fields.
type SubprocessTest struct {
	// The type of the file system to mount. Must be recognized by mount_sample.
	MountType string

	// Additional flags to be passed to the mount_sample tool.
	MountFlags []string

	// A list of files to pass to mount_sample. The given string flag will be
	// used to pass the file descriptor number.
	MountFiles map[string]*os.File

	// A context object that can be used for long-running operations.
	Ctx context.Context

	// The directory at which the file system is mounted.
	Dir string

	// Anothing non-nil in this slice will be closed by TearDown. The test will
	// fail if closing fails.
	ToClose []io.Closer

	mountSampleErr <-chan error
}

// Mount the file system and initialize the other exported fields of the
// struct. Panics on error.
//
// REQUIRES: t.FileSystem has been set.
func (t *SubprocessTest) SetUp(ti *ogletest.TestInfo) {
	err := t.initialize()
	if err != nil {
		panic(err)
	}
}

// Private state for getToolPath.
var getToolContents_Contents []byte
var getToolContents_Err error
var getToolContents_Once sync.Once

// Implementation detail of getToolPath.
func getToolContentsImpl() (contents []byte, err error) {
	// Fast path: has the user set the flag?
	if *fToolPath != "" {
		contents, err = ioutil.ReadFile(*fToolPath)
		if err != nil {
			err = fmt.Errorf("Reading mount_sample contents: %v", err)
			return
		}

		return
	}

	// Create a temporary directory into which we will compile the tool.
	tempDir, err := ioutil.TempDir("", "")
	if err != nil {
		err = fmt.Errorf("TempDir: %v", err)
		return
	}

	toolPath := path.Join(tempDir, "mount_sample")

	// Ensure that we kill the temporary directory when we're finished here.
	defer os.RemoveAll(tempDir)

	// Run "go build".
	cmd := exec.Command(
		"go",
		"build",
		"-o",
		toolPath,
		"github.com/jacobsa/fuse/samples/mount_sample")

	output, err := cmd.CombinedOutput()
	if err != nil {
		err = fmt.Errorf(
			"mount_sample exited with %v, output:\n%s",
			err,
			string(output))

		return
	}

	// Slurp the tool contents.
	contents, err = ioutil.ReadFile(toolPath)
	if err != nil {
		err = fmt.Errorf("ReadFile: %v", err)
		return
	}

	return
}

// Build the mount_sample tool if it has not yet been built for this process.
// Return its contents.
func getToolContents() (contents []byte, err error) {
	// Get hold of the binary contents, if we haven't yet.
	getToolContents_Once.Do(func() {
		getToolContents_Contents, getToolContents_Err = getToolContentsImpl()
	})

	contents, err = getToolContents_Contents, getToolContents_Err
	return
}

func waitForMountSample(
	cmd *exec.Cmd,
	errChan chan<- error,
	stderr *bytes.Buffer) {
	// However we exit, write the error to the channel.
	var err error
	defer func() {
		errChan <- err
	}()

	// Wait for the command.
	err = cmd.Wait()
	if err == nil {
		return
	}

	// Make exit errors nicer.
	if exitErr, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf(
			"mount_sample exited with %v. Stderr:\n%s",
			exitErr,
			stderr.String())

		return
	}

	err = fmt.Errorf("Waiting for mount_sample: %v", err)
}

func waitForReady(readyReader *os.File, c chan<- struct{}) {
	_, err := readyReader.Read(make([]byte, 1))
	if err != nil {
		log.Printf("Readying from ready pipe: %v", err)
		return
	}

	c <- struct{}{}
}

// Like SetUp, but doens't panic.
func (t *SubprocessTest) initialize() (err error) {
	// Initialize the context.
	t.Ctx = context.Background()

	// Set up a temporary directory.
	t.Dir, err = ioutil.TempDir("", "sample_test")
	if err != nil {
		err = fmt.Errorf("TempDir: %v", err)
		return
	}

	// Build/read the mount_sample tool.
	toolContents, err := getToolContents()
	if err != nil {
		err = fmt.Errorf("getTooltoolContents: %v", err)
		return
	}

	// Create a temporary file to hold the contents of the tool.
	toolFile, err := ioutil.TempFile("", "subprocess_test")
	if err != nil {
		err = fmt.Errorf("TempFile: %v", err)
		return
	}

	defer toolFile.Close()

	// Ensure that it is deleted when we leave.
	toolPath := toolFile.Name()
	defer os.Remove(toolPath)

	// Write out the tool contents and make them executable.
	if _, err = toolFile.Write(toolContents); err != nil {
		err = fmt.Errorf("toolFile.Write: %v", err)
		return
	}

	if err = toolFile.Chmod(0500); err != nil {
		err = fmt.Errorf("toolFile.Chmod: %v", err)
		return
	}

	// Close the tool file to prevent "text file busy" errors below.
	err = toolFile.Close()
	toolFile = nil
	if err != nil {
		err = fmt.Errorf("toolFile.Close: %v", err)
		return
	}

	// Set up basic args for the subprocess.
	args := []string{
		"--type",
		t.MountType,
		"--mount_point",
		t.Dir,
	}

	args = append(args, t.MountFlags...)

	// Set up a pipe for the "ready" status.
	readyReader, readyWriter, err := os.Pipe()
	if err != nil {
		err = fmt.Errorf("Pipe: %v", err)
		return
	}

	defer readyReader.Close()
	defer readyWriter.Close()

	t.MountFiles["ready_file"] = readyWriter

	// Set up inherited files and appropriate flags.
	var extraFiles []*os.File
	for flag, file := range t.MountFiles {
		// Cf. os/exec.Cmd.ExtraFiles
		fd := 3 + len(extraFiles)

		extraFiles = append(extraFiles, file)
		args = append(args, "--"+flag)
		args = append(args, fmt.Sprintf("%d", fd))
	}

	// Set up a command.
	var stderr bytes.Buffer
	mountCmd := exec.Command(toolPath, args...)
	mountCmd.Stderr = &stderr
	mountCmd.ExtraFiles = extraFiles

	// Start it.
	if err = mountCmd.Start(); err != nil {
		err = fmt.Errorf("mountCmd.Start: %v", err)
		return
	}

	// Launch a goroutine that waits for it and returns its status.
	mountSampleErr := make(chan error, 1)
	go waitForMountSample(mountCmd, mountSampleErr, &stderr)

	// Wait for the tool to say the file system is ready. In parallel, watch for
	// the tool to fail.
	readyChan := make(chan struct{}, 1)
	go waitForReady(readyReader, readyChan)

	select {
	case <-readyChan:
	case err = <-mountSampleErr:
		return
	}

	// TearDown is no responsible for joining.
	t.mountSampleErr = mountSampleErr

	return
}

// Unmount the file system and clean up. Panics on error.
func (t *SubprocessTest) TearDown() {
	err := t.destroy()
	if err != nil {
		panic(err)
	}
}

// Like TearDown, but doesn't panic.
func (t *SubprocessTest) destroy() (err error) {
	// Make sure we clean up after ourselves after everything else below.

	// Close what is necessary.
	for _, c := range t.ToClose {
		if c == nil {
			continue
		}

		ogletest.ExpectEq(nil, c.Close())
	}

	// If we didn't try to mount the file system, there's nothing further to do.
	if t.mountSampleErr == nil {
		return
	}

	// In the background, initiate an unmount.
	unmountErrChan := make(chan error)
	go func() {
		unmountErrChan <- unmount(t.Dir)
	}()

	// Make sure we wait for the unmount, even if we've already returned early in
	// error. Return its error if we haven't seen any other error.
	defer func() {
		// Wait.
		unmountErr := <-unmountErrChan
		if unmountErr != nil {
			if err != nil {
				log.Println("unmount:", unmountErr)
				return
			}

			err = fmt.Errorf("unmount: %v", unmountErr)
			return
		}

		// Clean up.
		ogletest.ExpectEq(nil, os.Remove(t.Dir))
	}()

	// Wait for the subprocess.
	if err = <-t.mountSampleErr; err != nil {
		return
	}

	return
}
<commit_msg>Be consistent about file names.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package samples

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"sync"

	"github.com/jacobsa/ogletest"
	"golang.org/x/net/context"
)

var fToolPath = flag.String(
	"mount_sample",
	"",
	"Path to the mount_sample tool. If unset, we will compile it.")

// A struct that implements common behavior needed by tests in the samples/
// directory where the file system is mounted by a subprocess. Use it as an
// embedded field in your test fixture, calling its SetUp method from your
// SetUp method after setting the MountType and MountFlags fields.
type SubprocessTest struct {
	// The type of the file system to mount. Must be recognized by mount_sample.
	MountType string

	// Additional flags to be passed to the mount_sample tool.
	MountFlags []string

	// A list of files to pass to mount_sample. The given string flag will be
	// used to pass the file descriptor number.
	MountFiles map[string]*os.File

	// A context object that can be used for long-running operations.
	Ctx context.Context

	// The directory at which the file system is mounted.
	Dir string

	// Anothing non-nil in this slice will be closed by TearDown. The test will
	// fail if closing fails.
	ToClose []io.Closer

	mountSampleErr <-chan error
}

// Mount the file system and initialize the other exported fields of the
// struct. Panics on error.
//
// REQUIRES: t.FileSystem has been set.
func (t *SubprocessTest) SetUp(ti *ogletest.TestInfo) {
	err := t.initialize()
	if err != nil {
		panic(err)
	}
}

// Private state for getToolPath.
var getToolContents_Contents []byte
var getToolContents_Err error
var getToolContents_Once sync.Once

// Implementation detail of getToolPath.
func getToolContentsImpl() (contents []byte, err error) {
	// Fast path: has the user set the flag?
	if *fToolPath != "" {
		contents, err = ioutil.ReadFile(*fToolPath)
		if err != nil {
			err = fmt.Errorf("Reading mount_sample contents: %v", err)
			return
		}

		return
	}

	// Create a temporary directory into which we will compile the tool.
	tempDir, err := ioutil.TempDir("", "sample_test")
	if err != nil {
		err = fmt.Errorf("TempDir: %v", err)
		return
	}

	toolPath := path.Join(tempDir, "mount_sample")

	// Ensure that we kill the temporary directory when we're finished here.
	defer os.RemoveAll(tempDir)

	// Run "go build".
	cmd := exec.Command(
		"go",
		"build",
		"-o",
		toolPath,
		"github.com/jacobsa/fuse/samples/mount_sample")

	output, err := cmd.CombinedOutput()
	if err != nil {
		err = fmt.Errorf(
			"mount_sample exited with %v, output:\n%s",
			err,
			string(output))

		return
	}

	// Slurp the tool contents.
	contents, err = ioutil.ReadFile(toolPath)
	if err != nil {
		err = fmt.Errorf("ReadFile: %v", err)
		return
	}

	return
}

// Build the mount_sample tool if it has not yet been built for this process.
// Return its contents.
func getToolContents() (contents []byte, err error) {
	// Get hold of the binary contents, if we haven't yet.
	getToolContents_Once.Do(func() {
		getToolContents_Contents, getToolContents_Err = getToolContentsImpl()
	})

	contents, err = getToolContents_Contents, getToolContents_Err
	return
}

func waitForMountSample(
	cmd *exec.Cmd,
	errChan chan<- error,
	stderr *bytes.Buffer) {
	// However we exit, write the error to the channel.
	var err error
	defer func() {
		errChan <- err
	}()

	// Wait for the command.
	err = cmd.Wait()
	if err == nil {
		return
	}

	// Make exit errors nicer.
	if exitErr, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf(
			"mount_sample exited with %v. Stderr:\n%s",
			exitErr,
			stderr.String())

		return
	}

	err = fmt.Errorf("Waiting for mount_sample: %v", err)
}

func waitForReady(readyReader *os.File, c chan<- struct{}) {
	_, err := readyReader.Read(make([]byte, 1))
	if err != nil {
		log.Printf("Readying from ready pipe: %v", err)
		return
	}

	c <- struct{}{}
}

// Like SetUp, but doens't panic.
func (t *SubprocessTest) initialize() (err error) {
	// Initialize the context.
	t.Ctx = context.Background()

	// Set up a temporary directory.
	t.Dir, err = ioutil.TempDir("", "sample_test")
	if err != nil {
		err = fmt.Errorf("TempDir: %v", err)
		return
	}

	// Build/read the mount_sample tool.
	toolContents, err := getToolContents()
	if err != nil {
		err = fmt.Errorf("getTooltoolContents: %v", err)
		return
	}

	// Create a temporary file to hold the contents of the tool.
	toolFile, err := ioutil.TempFile("", "sample_test")
	if err != nil {
		err = fmt.Errorf("TempFile: %v", err)
		return
	}

	defer toolFile.Close()

	// Ensure that it is deleted when we leave.
	toolPath := toolFile.Name()
	defer os.Remove(toolPath)

	// Write out the tool contents and make them executable.
	if _, err = toolFile.Write(toolContents); err != nil {
		err = fmt.Errorf("toolFile.Write: %v", err)
		return
	}

	if err = toolFile.Chmod(0500); err != nil {
		err = fmt.Errorf("toolFile.Chmod: %v", err)
		return
	}

	// Close the tool file to prevent "text file busy" errors below.
	err = toolFile.Close()
	toolFile = nil
	if err != nil {
		err = fmt.Errorf("toolFile.Close: %v", err)
		return
	}

	// Set up basic args for the subprocess.
	args := []string{
		"--type",
		t.MountType,
		"--mount_point",
		t.Dir,
	}

	args = append(args, t.MountFlags...)

	// Set up a pipe for the "ready" status.
	readyReader, readyWriter, err := os.Pipe()
	if err != nil {
		err = fmt.Errorf("Pipe: %v", err)
		return
	}

	defer readyReader.Close()
	defer readyWriter.Close()

	t.MountFiles["ready_file"] = readyWriter

	// Set up inherited files and appropriate flags.
	var extraFiles []*os.File
	for flag, file := range t.MountFiles {
		// Cf. os/exec.Cmd.ExtraFiles
		fd := 3 + len(extraFiles)

		extraFiles = append(extraFiles, file)
		args = append(args, "--"+flag)
		args = append(args, fmt.Sprintf("%d", fd))
	}

	// Set up a command.
	var stderr bytes.Buffer
	mountCmd := exec.Command(toolPath, args...)
	mountCmd.Stderr = &stderr
	mountCmd.ExtraFiles = extraFiles

	// Start it.
	if err = mountCmd.Start(); err != nil {
		err = fmt.Errorf("mountCmd.Start: %v", err)
		return
	}

	// Launch a goroutine that waits for it and returns its status.
	mountSampleErr := make(chan error, 1)
	go waitForMountSample(mountCmd, mountSampleErr, &stderr)

	// Wait for the tool to say the file system is ready. In parallel, watch for
	// the tool to fail.
	readyChan := make(chan struct{}, 1)
	go waitForReady(readyReader, readyChan)

	select {
	case <-readyChan:
	case err = <-mountSampleErr:
		return
	}

	// TearDown is no responsible for joining.
	t.mountSampleErr = mountSampleErr

	return
}

// Unmount the file system and clean up. Panics on error.
func (t *SubprocessTest) TearDown() {
	err := t.destroy()
	if err != nil {
		panic(err)
	}
}

// Like TearDown, but doesn't panic.
func (t *SubprocessTest) destroy() (err error) {
	// Make sure we clean up after ourselves after everything else below.

	// Close what is necessary.
	for _, c := range t.ToClose {
		if c == nil {
			continue
		}

		ogletest.ExpectEq(nil, c.Close())
	}

	// If we didn't try to mount the file system, there's nothing further to do.
	if t.mountSampleErr == nil {
		return
	}

	// In the background, initiate an unmount.
	unmountErrChan := make(chan error)
	go func() {
		unmountErrChan <- unmount(t.Dir)
	}()

	// Make sure we wait for the unmount, even if we've already returned early in
	// error. Return its error if we haven't seen any other error.
	defer func() {
		// Wait.
		unmountErr := <-unmountErrChan
		if unmountErr != nil {
			if err != nil {
				log.Println("unmount:", unmountErr)
				return
			}

			err = fmt.Errorf("unmount: %v", unmountErr)
			return
		}

		// Clean up.
		ogletest.ExpectEq(nil, os.Remove(t.Dir))
	}()

	// Wait for the subprocess.
	if err = <-t.mountSampleErr; err != nil {
		return
	}

	return
}
<|endoftext|>


<commit_before>package util

import (
	"github.com/ghts/ghts/lib"
	"github.com/ghts/ghts/lib/daily_price_data"
	"github.com/ghts/ghts/lib/krx_time"
	xt "github.com/ghts/ghts/xing/base"
	xing "github.com/ghts/ghts/xing/go"

	"bytes"
	"database/sql"
	"time"
)

func F당일_일일_가격정보_수집(db *sql.DB) (에러 error) {
	if krx.F한국증시_동시호가_시간임() || krx.F한국증시_정규_거래_시간임() {
		lib.F문자열_출력("장 중에는 정확한 데이터를 수집할 수 없습니다.")
		return
	}

	daily_price_data.F일일_가격정보_테이블_생성(db)

	당일 := lib.F일자2정수(xing.F당일())
	현재가_맵, 에러 := xing.TrT8407_현물_멀티_현재가_조회_전종목()
	lib.F확인(에러)

	for 종목코드, 값 := range 현재가_맵 {
		s := new(daily_price_data.S일일_가격정보)
		s.M종목코드 = 종목코드
		s.M일자 = 당일
		s.M시가 = float64(값.M시가)
		s.M고가 = float64(값.M고가)
		s.M저가 = float64(값.M저가)
		s.M종가 = float64(값.M현재가)
		s.M거래량 = float64(값.M누적_거래량)

		종목별_일일_가격정보_모음, 에러 := daily_price_data.New종목별_일일_가격정보_모음([]*daily_price_data.S일일_가격정보{s})
		lib.F확인(에러)

		lib.F확인(종목별_일일_가격정보_모음.DB저장(db))
	}

	lib.F문자열_출력("당일 가격정보 수집 완료.")

	return nil
}

func F일개월_일일_가격정보_수집(db *sql.DB, 종목코드_모음 []string) (에러 error) {
	return f고정_기간_일일_가격정보_수집(db, 종목코드_모음, 31*lib.P1일)
}

func F일년_일일_가격정보_수집(db *sql.DB, 종목코드_모음 []string) (에러 error) {
	return f고정_기간_일일_가격정보_수집(db, 종목코드_모음, lib.P1년)
}

func f고정_기간_일일_가격정보_수집(db *sql.DB, 종목코드_모음 []string, 기간 time.Duration) (에러 error) {
	daily_price_data.F일일_가격정보_테이블_생성(db)

	시작일 := lib.F금일().Add(-1 * 기간)

	for i, 종목코드 := range 종목코드_모음 {
		f일일_가격정보_수집_도우미(db, 종목코드, 시작일, i)
	}

	return nil
}

func F일일_가격정보_수집(db *sql.DB, 종목코드_모음 []string) (에러 error) {
	var 시작일, 마지막_저장일 time.Time
	var 종목별_일일_가격정보_모음 *daily_price_data.S종목별_일일_가격정보_모음

	daily_price_data.F일일_가격정보_테이블_생성(db)

	출력_문자열_버퍼 := new(bytes.Buffer)

	for i, 종목코드 := range 종목코드_모음 {
		종목별_일일_가격정보_모음, 에러 = daily_price_data.New종목별_일일_가격정보_모음_DB읽기(db, 종목코드)
		lib.F확인(에러)

		// 시작일 설정
		시작일 = lib.F지금().AddDate(-30, 0, 0)
		if 에러 == nil && len(종목별_일일_가격정보_모음.M저장소) > 0 {
			// lib.S종목별_일일_가격정보_모음 는 일자 순서로 정렬되어 있음.
			마지막_저장일 = 종목별_일일_가격정보_모음.M저장소[len(종목별_일일_가격정보_모음.M저장소)-1].G일자2()
			시작일 = 마지막_저장일.AddDate(0, 0, 1)
		}

		if 시작일.After(xing.F당일()) {
			//lib.F문자열_출력("%v [%v] : 최신 데이터 업데이트.", i, 종목코드)
			continue
		} else if 시작일.After(lib.F금일().AddDate(0, 0, -14)) {
			// 데이터 수량이 1개이나 100개이나 소요 시간은 비슷함.
			시작일 = lib.F금일().AddDate(0, 0, -14)
		}

		f일일_가격정보_수집_도우미(db, 종목코드, 시작일, i, 출력_문자열_버퍼)
	}

	lib.F문자열_출력(출력_문자열_버퍼.String())

	return nil
}

func f일일_가격정보_수집_도우미(db *sql.DB, 종목코드 string, 시작일 time.Time, i int, 버퍼 ...*bytes.Buffer) {
	var 종료일 time.Time

	// 종료일 설정
	if lib.F지금().After(xing.F당일().Add(15*lib.P1시간 + lib.P30분)) {
		종료일 = xing.F당일()
	} else {
		종료일 = xing.F전일()
	}

	// 시작일 오류 확인
	if 시작일 = lib.F2일자(시작일); 시작일.After(종료일) {
		return
	} else if 시작일.Equal(종료일) { // 시작일과 종료일이 같으면 수천 개의 데이터를 불러오는 현상이 있음.
		시작일 = 시작일.AddDate(0, 0, -1)
	}

	// 데이터 수집
	값_모음, 에러 := xing.TrT8413_현물_차트_일주월(종목코드, 시작일, 종료일, xt.P일주월_일)
	if 에러 != nil {
		lib.F에러_출력(에러)
		return
	} else if len(값_모음) == 0 {
		lib.F체크포인트(i, 종목코드, "추가 저장할 데이터가 없음.")
		return // 추가 저장할 데이터가 없음.
	}

	일일_가격정보_슬라이스 := make([]*daily_price_data.S일일_가격정보, len(값_모음))

	for i, 일일_데이터 := range 값_모음 {
		일일_가격정보_슬라이스[i] = daily_price_data.New일일_가격정보(
			일일_데이터.M종목코드,
			일일_데이터.M일자,
			일일_데이터.M시가,
			일일_데이터.M고가,
			일일_데이터.M저가,
			일일_데이터.M종가,
			일일_데이터.M거래량)
	}

	출력_문자열 := lib.F2문자열("%v %v %v~%v %v개\n", i+1, xing.F종목_식별_문자열(종목코드), 시작일.Format(lib.P일자_형식), 종료일.Format(lib.P일자_형식), len(값_모음))

	if len(버퍼) > 0 && 버퍼[0] != nil {
		// 버퍼가 존재하면 버퍼에 출력
		버퍼[0].WriteString(출력_문자열)
	} else {
		lib.F문자열_출력(출력_문자열)
	}

	종목별_일일_가격정보_모음, 에러 := daily_price_data.New종목별_일일_가격정보_모음(일일_가격정보_슬라이스)
	if 에러 != nil {
		lib.F에러_출력(에러)
		return
	}

	lib.F확인(종목별_일일_가격정보_모음.DB저장(db))
}
<commit_msg>일일 가격정보 수집 종목 순서 랜덤화.<commit_after>package util

import (
	"github.com/ghts/ghts/lib"
	"github.com/ghts/ghts/lib/daily_price_data"
	"github.com/ghts/ghts/lib/krx_time"
	xt "github.com/ghts/ghts/xing/base"
	xing "github.com/ghts/ghts/xing/go"

	"bytes"
	"database/sql"
	"time"
)

func F당일_일일_가격정보_수집(db *sql.DB) (에러 error) {
	if krx.F한국증시_동시호가_시간임() || krx.F한국증시_정규_거래_시간임() {
		lib.F문자열_출력("장 중에는 정확한 데이터를 수집할 수 없습니다.")
		return
	}

	daily_price_data.F일일_가격정보_테이블_생성(db)

	당일 := lib.F일자2정수(xing.F당일())
	현재가_맵, 에러 := xing.TrT8407_현물_멀티_현재가_조회_전종목()
	lib.F확인(에러)

	for 종목코드, 값 := range 현재가_맵 {
		s := new(daily_price_data.S일일_가격정보)
		s.M종목코드 = 종목코드
		s.M일자 = 당일
		s.M시가 = float64(값.M시가)
		s.M고가 = float64(값.M고가)
		s.M저가 = float64(값.M저가)
		s.M종가 = float64(값.M현재가)
		s.M거래량 = float64(값.M누적_거래량)

		종목별_일일_가격정보_모음, 에러 := daily_price_data.New종목별_일일_가격정보_모음([]*daily_price_data.S일일_가격정보{s})
		lib.F확인(에러)

		lib.F확인(종목별_일일_가격정보_모음.DB저장(db))
	}

	lib.F문자열_출력("당일 가격정보 수집 완료.")

	return nil
}

func F일개월_일일_가격정보_수집(db *sql.DB, 종목코드_모음 []string) (에러 error) {
	return f고정_기간_일일_가격정보_수집(db, 종목코드_모음, 31*lib.P1일)
}

func F일년_일일_가격정보_수집(db *sql.DB, 종목코드_모음 []string) (에러 error) {
	return f고정_기간_일일_가격정보_수집(db, 종목코드_모음, lib.P1년)
}

func f고정_기간_일일_가격정보_수집(db *sql.DB, 종목코드_모음 []string, 기간 time.Duration) (에러 error) {
	daily_price_data.F일일_가격정보_테이블_생성(db)

	시작일 := lib.F금일().Add(-1 * 기간)

	// 종목 순서를 매번 랜덤화 시켜서 반복 실행 시 나중 종목만 누락되는 현상을 방지하기 위해서 맵에 대입.
	종목코드_맵 := make(map[string]lib.S비어있음)

	for _, 종목코드 := range 종목코드_모음 {
		종목코드_맵[종목코드] = lib.F비어있는_값()
	}

	i := 0

	for 종목코드 := range 종목코드_맵 {
		f일일_가격정보_수집_도우미(db, 종목코드, 시작일, i)
	}

	return nil
}

func F일일_가격정보_수집(db *sql.DB, 종목코드_모음 []string) (에러 error) {
	var 시작일, 마지막_저장일 time.Time
	var 종목별_일일_가격정보_모음 *daily_price_data.S종목별_일일_가격정보_모음

	daily_price_data.F일일_가격정보_테이블_생성(db)

	출력_문자열_버퍼 := new(bytes.Buffer)

	for i, 종목코드 := range 종목코드_모음 {
		종목별_일일_가격정보_모음, 에러 = daily_price_data.New종목별_일일_가격정보_모음_DB읽기(db, 종목코드)
		lib.F확인(에러)

		// 시작일 설정
		시작일 = lib.F지금().AddDate(-30, 0, 0)
		if 에러 == nil && len(종목별_일일_가격정보_모음.M저장소) > 0 {
			// lib.S종목별_일일_가격정보_모음 는 일자 순서로 정렬되어 있음.
			마지막_저장일 = 종목별_일일_가격정보_모음.M저장소[len(종목별_일일_가격정보_모음.M저장소)-1].G일자2()
			시작일 = 마지막_저장일.AddDate(0, 0, 1)
		}

		if 시작일.After(xing.F당일()) {
			//lib.F문자열_출력("%v [%v] : 최신 데이터 업데이트.", i, 종목코드)
			continue
		} else if 시작일.After(lib.F금일().AddDate(0, 0, -14)) {
			// 데이터 수량이 1개이나 100개이나 소요 시간은 비슷함.
			시작일 = lib.F금일().AddDate(0, 0, -14)
		}

		f일일_가격정보_수집_도우미(db, 종목코드, 시작일, i, 출력_문자열_버퍼)
	}

	lib.F문자열_출력(출력_문자열_버퍼.String())

	return nil
}

func f일일_가격정보_수집_도우미(db *sql.DB, 종목코드 string, 시작일 time.Time, i int, 버퍼 ...*bytes.Buffer) {
	var 종료일 time.Time

	// 종료일 설정
	if lib.F지금().After(xing.F당일().Add(15*lib.P1시간 + lib.P30분)) {
		종료일 = xing.F당일()
	} else {
		종료일 = xing.F전일()
	}

	// 시작일 오류 확인
	if 시작일 = lib.F2일자(시작일); 시작일.After(종료일) {
		return
	} else if 시작일.Equal(종료일) { // 시작일과 종료일이 같으면 수천 개의 데이터를 불러오는 현상이 있음.
		시작일 = 시작일.AddDate(0, 0, -1)
	}

	// 데이터 수집
	값_모음, 에러 := xing.TrT8413_현물_차트_일주월(종목코드, 시작일, 종료일, xt.P일주월_일)
	if 에러 != nil {
		lib.F에러_출력(에러)
		return
	} else if len(값_모음) == 0 {
		lib.F체크포인트(i, 종목코드, "추가 저장할 데이터가 없음.")
		return // 추가 저장할 데이터가 없음.
	}

	일일_가격정보_슬라이스 := make([]*daily_price_data.S일일_가격정보, len(값_모음))

	for i, 일일_데이터 := range 값_모음 {
		일일_가격정보_슬라이스[i] = daily_price_data.New일일_가격정보(
			일일_데이터.M종목코드,
			일일_데이터.M일자,
			일일_데이터.M시가,
			일일_데이터.M고가,
			일일_데이터.M저가,
			일일_데이터.M종가,
			일일_데이터.M거래량)
	}

	출력_문자열 := lib.F2문자열("%v %v %v~%v %v개\n", i+1, xing.F종목_식별_문자열(종목코드), 시작일.Format(lib.P일자_형식), 종료일.Format(lib.P일자_형식), len(값_모음))

	if len(버퍼) > 0 && 버퍼[0] != nil {
		// 버퍼가 존재하면 버퍼에 출력
		버퍼[0].WriteString(출력_문자열)
	} else {
		lib.F문자열_출력(출력_문자열)
	}

	종목별_일일_가격정보_모음, 에러 := daily_price_data.New종목별_일일_가격정보_모음(일일_가격정보_슬라이스)
	if 에러 != nil {
		lib.F에러_출력(에러)
		return
	}

	lib.F확인(종목별_일일_가격정보_모음.DB저장(db))
}
<|endoftext|>