<commit_before>package main

import (
	"context"
	"fmt"
	"github.com/google/go-github/github"
	"io"
	"net/http"
	"regexp"
)

type protection interface {
	protect(repo *github.Repository)
	free(repo *github.Repository)
}

type githubProtection struct {
	repositoriesService repositoriesService
	branchPatterns      []*regexp.Regexp
	successOutput       io.Writer
	failureOutput       io.Writer
}

type success string
type failure string

func (gp *githubProtection) process(repo *github.Repository, modify func(*github.Branch) (success, failure)) {
	if (*repo.Permissions)["admin"] == false {
		fmt.Fprintf(gp.failureOutput, "%s: you don't have admin rights to modify this repository\n", *repo.FullName)
		return
	}

	branches, err := gp.filterBranches(repo)
	if err != nil {
		fmt.Fprint(gp.failureOutput, err)
	}

	for _, branch := range branches {
		success, failure := modify(branch)
		if failure != "" {
			fmt.Fprintln(gp.failureOutput, failure)
		} else {
			fmt.Fprintln(gp.successOutput, success)
		}
	}
}

func (gp *githubProtection) protect(repo *github.Repository) {
	gp.process(repo, func(branch *github.Branch) (success, failure) {
		return gp.lock(repo, *branch.Name)
	})
}

func (gp *githubProtection) free(repo *github.Repository) {
	gp.process(repo, func(branch *github.Branch) (success, failure) {
		return gp.unlock(repo, *branch.Name)
	})
}

func (gp *githubProtection) filterBranches(repo *github.Repository) ([]*github.Branch, error) {
	opt := &github.ListOptions{
		PerPage: 100,
	}

	branches, resp, err := gp.repositoriesService.ListBranches(context.TODO(), *repo.Owner.Login, *repo.Name, opt)

	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Received HTTP response [%s] when listing branches for %s", resp.Status, repo.FullName)
	}

	result := make([]*github.Branch, 0)
	for _, branch := range branches {
		if gp.accept(*branch.Name) {
			result = append(result, branch)
		}
	}

	return result, nil
}

func withRepo(msg string, repo *github.Repository, branch *github.Branch) string {
	return fmt.Sprintf("%s: %s %s", *repo.FullName, *branch.Name, msg)
}

func (gp *githubProtection) lock(repo *github.Repository, branchName string) (success, failure) {
	branch, _, err := gp.repositoriesService.GetBranch(context.TODO(), *repo.Owner.Login, *repo.Name, branchName)
	if err != nil {
		return "", failure(withRepo(err.Error(), repo, branch))
	}

	if *branch.Protected {
		return success(withRepo("is already protected", repo, branch)), ""
	}

	if dryrun {
		return success(withRepo("will be set to protected", repo, branch)), ""
	}

	activateProtection := true
	branch.Protected = &activateProtection
	protectionReq := &github.ProtectionRequest{
		RequiredStatusChecks: nil,
		Restrictions:         nil,
	}
	if _, _, err := gp.repositoriesService.UpdateBranchProtection(context.TODO(), *repo.Owner.Login, *repo.Name, *branch.Name, protectionReq); err != nil {
		return "", failure(withRepo(err.Error(), repo, branch))
	}

	return success(withRepo("is now protected", repo, branch)), ""
}

func (gp *githubProtection) unlock(repo *github.Repository, branchName string) (success, failure) {
	branch, _, err := gp.repositoriesService.GetBranch(context.TODO(), *repo.Owner.Login, *repo.Name, branchName)
	if err != nil {
		return "", failure(withRepo(err.Error(), repo, branch))
	}

	if !*branch.Protected {
		return success(withRepo("is already unprotected", repo, branch)), ""
	}

	if dryrun {
		return success(withRepo("will be freed", repo, branch)), ""
	}

	if _, err := gp.repositoriesService.RemoveBranchProtection(context.TODO(), *repo.Owner.Login, *repo.Name, *branch.Name); err != nil {
		return "", failure(withRepo(err.Error(), repo, branch))
	}

	return success(withRepo("is now free", repo, branch)), ""
}

func (gp *githubProtection) accept(branchName string) bool {
	for _, toProtect := range gp.branchPatterns {
		if toProtect.MatchString(branchName) {
			return true
		}
	}
	return false
}
<commit_msg>Use value instead of reference to print a message<commit_after>package main

import (
	"context"
	"fmt"
	"github.com/google/go-github/github"
	"io"
	"net/http"
	"regexp"
)

type protection interface {
	protect(repo *github.Repository)
	free(repo *github.Repository)
}

type githubProtection struct {
	repositoriesService repositoriesService
	branchPatterns      []*regexp.Regexp
	successOutput       io.Writer
	failureOutput       io.Writer
}

type success string
type failure string

func (gp *githubProtection) process(repo *github.Repository, modify func(*github.Branch) (success, failure)) {
	if (*repo.Permissions)["admin"] == false {
		fmt.Fprintf(gp.failureOutput, "%s: you don't have admin rights to modify this repository\n", *repo.FullName)
		return
	}

	branches, err := gp.filterBranches(repo)
	if err != nil {
		fmt.Fprint(gp.failureOutput, err)
	}

	for _, branch := range branches {
		success, failure := modify(branch)
		if failure != "" {
			fmt.Fprintln(gp.failureOutput, failure)
		} else {
			fmt.Fprintln(gp.successOutput, success)
		}
	}
}

func (gp *githubProtection) protect(repo *github.Repository) {
	gp.process(repo, func(branch *github.Branch) (success, failure) {
		return gp.lock(repo, *branch.Name)
	})
}

func (gp *githubProtection) free(repo *github.Repository) {
	gp.process(repo, func(branch *github.Branch) (success, failure) {
		return gp.unlock(repo, *branch.Name)
	})
}

func (gp *githubProtection) filterBranches(repo *github.Repository) ([]*github.Branch, error) {
	opt := &github.ListOptions{
		PerPage: 100,
	}

	branches, resp, err := gp.repositoriesService.ListBranches(context.TODO(), *repo.Owner.Login, *repo.Name, opt)

	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("Received HTTP response [%s] when listing branches for %s", resp.Status, *repo.FullName)
	}

	result := make([]*github.Branch, 0)
	for _, branch := range branches {
		if gp.accept(*branch.Name) {
			result = append(result, branch)
		}
	}

	return result, nil
}

func withRepo(msg string, repo *github.Repository, branch *github.Branch) string {
	return fmt.Sprintf("%s: %s %s", *repo.FullName, *branch.Name, msg)
}

func (gp *githubProtection) lock(repo *github.Repository, branchName string) (success, failure) {
	branch, _, err := gp.repositoriesService.GetBranch(context.TODO(), *repo.Owner.Login, *repo.Name, branchName)
	if err != nil {
		return "", failure(withRepo(err.Error(), repo, branch))
	}

	if *branch.Protected {
		return success(withRepo("is already protected", repo, branch)), ""
	}

	if dryrun {
		return success(withRepo("will be set to protected", repo, branch)), ""
	}

	activateProtection := true
	branch.Protected = &activateProtection
	protectionReq := &github.ProtectionRequest{
		RequiredStatusChecks: nil,
		Restrictions:         nil,
	}
	if _, _, err := gp.repositoriesService.UpdateBranchProtection(context.TODO(), *repo.Owner.Login, *repo.Name, *branch.Name, protectionReq); err != nil {
		return "", failure(withRepo(err.Error(), repo, branch))
	}

	return success(withRepo("is now protected", repo, branch)), ""
}

func (gp *githubProtection) unlock(repo *github.Repository, branchName string) (success, failure) {
	branch, _, err := gp.repositoriesService.GetBranch(context.TODO(), *repo.Owner.Login, *repo.Name, branchName)
	if err != nil {
		return "", failure(withRepo(err.Error(), repo, branch))
	}

	if !*branch.Protected {
		return success(withRepo("is already unprotected", repo, branch)), ""
	}

	if dryrun {
		return success(withRepo("will be freed", repo, branch)), ""
	}

	if _, err := gp.repositoriesService.RemoveBranchProtection(context.TODO(), *repo.Owner.Login, *repo.Name, *branch.Name); err != nil {
		return "", failure(withRepo(err.Error(), repo, branch))
	}

	return success(withRepo("is now free", repo, branch)), ""
}

func (gp *githubProtection) accept(branchName string) bool {
	for _, toProtect := range gp.branchPatterns {
		if toProtect.MatchString(branchName) {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package backend

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"sync"
)

const (
	defaultVersion = "2.0.0"
)

type backendData struct {
	version             string
	path                string
	createEnvFastFail   bool
	deleteEnvFastFail   bool
	callRealInterpolate bool
	interpolateArgs     []string
	createEnvArgs       string
	createEnvCallCount  int
}

type Backend struct {
	server       *httptest.Server
	handlerMutex sync.Mutex
	backendMutex sync.Mutex
	handler      func(w http.ResponseWriter, r *http.Request)

	backend *backendData
}

func NewBackend() *Backend {
	backend := &Backend{
		backend: &backendData{},
	}
	backend.server = httptest.NewServer(http.HandlerFunc(backend.ServeHTTP))
	backend.ResetAll()

	return backend
}

func (b *Backend) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()
	if b.handler != nil {
		b.handler(w, r)
	} else {
		b.defaultHandler(w, r)
	}
}

func (b *Backend) defaultHandler(responseWriter http.ResponseWriter, request *http.Request) {
	switch request.URL.Path {
	case "/version":
		b.handleVersion(responseWriter)
	case "/path":
		b.handlePath(responseWriter)
	case "/interpolate/args":
		b.handleInterpolateArgs(request)
	case "/create-env/args":
		b.handleCreateEnvArgs(request)
	case "/create-env/fastfail":
		b.handleCreateEnvFastFail(responseWriter)
	case "/create-env/call-count":
		b.handleCreateEnvCallCount(responseWriter)
	case "/delete-env/fastfail":
		b.handleDeleteEnvFastFail(responseWriter)
	case "/call-real-interpolate":
		b.handleCallRealInterpolate(responseWriter)
	default:
		responseWriter.WriteHeader(http.StatusOK)
		return
	}
}

func (b *Backend) ResetAll() {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()

	path := b.backend.path
	b.backend = &backendData{
		version: defaultVersion,
		path:    path,
	}
}

func (b *Backend) SetVersion(version string) {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()

	b.backend.version = version
}

func (b *Backend) ResetVersion() {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()

	b.backend.version = defaultVersion
}

func (b *Backend) SetHandler(f func(w http.ResponseWriter, r *http.Request)) {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()

	b.handler = f
}

func (b *Backend) ServerURL() string {
	return b.server.URL
}

func (b *Backend) SetPath(path string) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.path = path
}

func (b *Backend) SetCreateEnvFastFail(fastFail bool) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.createEnvFastFail = fastFail
}

func (b *Backend) SetDeleteEnvFastFail(fastFail bool) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.deleteEnvFastFail = fastFail
}

func (b *Backend) SetCallRealInterpolate(callRealInterpolate bool) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.callRealInterpolate = callRealInterpolate
}

func (b *Backend) ResetInterpolateArgs() {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.interpolateArgs = []string{}
}

func (b *Backend) GetInterpolateArgs(index int) string {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	return b.backend.interpolateArgs[index]
}

func (b *Backend) CreateEnvCallCount() int {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	return b.backend.createEnvCallCount
}

func (b *Backend) handleInterpolateArgs(request *http.Request) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	body, err := ioutil.ReadAll(request.Body)
	if err != nil {
		panic(err)
	}
	b.backend.interpolateArgs = append(b.backend.interpolateArgs, string(body))
}

func (b *Backend) handleCreateEnvArgs(request *http.Request) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	body, err := ioutil.ReadAll(request.Body)
	if err != nil {
		panic(err)
	}
	b.backend.createEnvArgs = string(body)
}

func (b *Backend) handleCreateEnvFastFail(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	if b.backend.createEnvFastFail {
		responseWriter.WriteHeader(http.StatusInternalServerError)
	} else {
		responseWriter.WriteHeader(http.StatusOK)
	}
}

func (b *Backend) handleCreateEnvCallCount(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.createEnvCallCount++
}

func (b *Backend) handleDeleteEnvFastFail(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	if b.backend.deleteEnvFastFail {
		responseWriter.WriteHeader(http.StatusInternalServerError)
	} else {
		responseWriter.WriteHeader(http.StatusOK)
	}
}

func (b *Backend) handleCallRealInterpolate(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	if b.backend.callRealInterpolate {
		responseWriter.Write([]byte("true"))
	} else {
		responseWriter.Write([]byte("false"))
	}
}

func (b *Backend) handleVersion(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	responseWriter.Write([]byte(b.backend.version))
}

func (b *Backend) handlePath(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	responseWriter.Write([]byte(b.backend.path))
}
<commit_msg>refactor fake bosh backend<commit_after>package backend

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"sync"
)

const (
	defaultVersion = "2.0.0"
)

type backendData struct {
	version             string
	path                string
	callRealInterpolate bool

	createEnvCall struct {
		fastFail  bool
		args      string
		callCount int
	}
	deleteEnvCall struct {
		fastFail bool
	}
	interpolateCall struct {
		args []string
	}
}

type Backend struct {
	server       *httptest.Server
	handlerMutex sync.Mutex
	backendMutex sync.Mutex
	handler      func(w http.ResponseWriter, r *http.Request)

	backend *backendData
}

func NewBackend() *Backend {
	backend := &Backend{
		backend: &backendData{},
	}
	backend.server = httptest.NewServer(http.HandlerFunc(backend.ServeHTTP))
	backend.ResetAll()

	return backend
}

func (b *Backend) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()
	if b.handler != nil {
		b.handler(w, r)
	} else {
		b.defaultHandler(w, r)
	}
}

func (b *Backend) defaultHandler(responseWriter http.ResponseWriter, request *http.Request) {
	switch request.URL.Path {
	case "/version":
		b.handleVersion(responseWriter)
	case "/path":
		b.handlePath(responseWriter)
	case "/interpolate/args":
		b.handleInterpolateArgs(request)
	case "/create-env/args":
		b.handleCreateEnvArgs(request)
	case "/create-env/fastfail":
		b.handleCreateEnvFastFail(responseWriter)
	case "/create-env/call-count":
		b.handleCreateEnvCallCount(responseWriter)
	case "/delete-env/fastfail":
		b.handleDeleteEnvFastFail(responseWriter)
	case "/call-real-interpolate":
		b.handleCallRealInterpolate(responseWriter)
	default:
		responseWriter.WriteHeader(http.StatusOK)
		return
	}
}

func (b *Backend) ResetAll() {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()

	path := b.backend.path
	b.backend = &backendData{
		version: defaultVersion,
		path:    path,
	}
}

func (b *Backend) SetVersion(version string) {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()

	b.backend.version = version
}

func (b *Backend) ResetVersion() {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()

	b.backend.version = defaultVersion
}

func (b *Backend) SetHandler(f func(w http.ResponseWriter, r *http.Request)) {
	b.handlerMutex.Lock()
	defer b.handlerMutex.Unlock()

	b.handler = f
}

func (b *Backend) ServerURL() string {
	return b.server.URL
}

func (b *Backend) SetPath(path string) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.path = path
}

func (b *Backend) SetCreateEnvFastFail(fastFail bool) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.createEnvCall.fastFail = fastFail
}

func (b *Backend) SetDeleteEnvFastFail(fastFail bool) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.deleteEnvCall.fastFail = fastFail
}

func (b *Backend) SetCallRealInterpolate(callRealInterpolate bool) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.callRealInterpolate = callRealInterpolate
}

func (b *Backend) ResetInterpolateArgs() {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.interpolateCall.args = []string{}
}

func (b *Backend) GetInterpolateArgs(index int) string {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	return b.backend.interpolateCall.args[index]
}

func (b *Backend) CreateEnvCallCount() int {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	return b.backend.createEnvCall.callCount
}

func (b *Backend) handleInterpolateArgs(request *http.Request) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	body, err := ioutil.ReadAll(request.Body)
	if err != nil {
		panic(err)
	}
	b.backend.interpolateCall.args = append(b.backend.interpolateCall.args, string(body))
}

func (b *Backend) handleCreateEnvArgs(request *http.Request) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	body, err := ioutil.ReadAll(request.Body)
	if err != nil {
		panic(err)
	}
	b.backend.createEnvCall.args = string(body)
}

func (b *Backend) handleCreateEnvFastFail(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	if b.backend.createEnvCall.fastFail {
		responseWriter.WriteHeader(http.StatusInternalServerError)
	} else {
		responseWriter.WriteHeader(http.StatusOK)
	}
}

func (b *Backend) handleCreateEnvCallCount(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	b.backend.createEnvCall.callCount++
}

func (b *Backend) handleDeleteEnvFastFail(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	if b.backend.deleteEnvCall.fastFail {
		responseWriter.WriteHeader(http.StatusInternalServerError)
	} else {
		responseWriter.WriteHeader(http.StatusOK)
	}
}

func (b *Backend) handleCallRealInterpolate(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	if b.backend.callRealInterpolate {
		responseWriter.Write([]byte("true"))
	} else {
		responseWriter.Write([]byte("false"))
	}
}

func (b *Backend) handleVersion(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	responseWriter.Write([]byte(b.backend.version))
}

func (b *Backend) handlePath(responseWriter http.ResponseWriter) {
	b.backendMutex.Lock()
	defer b.backendMutex.Unlock()

	responseWriter.Write([]byte(b.backend.path))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/d-bf/client/dbf"
	"github.com/d-bf/client/term"
	"os"
	"path/filepath"
	"time"
)

var timer uint

func deferPanic() {
	if panicVal := recover(); panicVal != nil { // Recovering from panic
		if exitCode, ok := panicVal.(int); ok { // Panic value is integer
			os.Exit(exitCode) // Exit with the integer panic value
		}
	}
}

func initialize() {
	fmt.Println("Initializing...")

	// Set data path
	pathData, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err == nil {
		dbf.PathData = pathData + string(os.PathSeparator) + "dbf-data" + string(os.PathSeparator)
	} else {
		fmt.Fprintf(os.Stderr, "Can not set current path. Error: %s\n", err)
		panic(1)
	}

	// Check data folder
	if _, err = os.Stat(dbf.PathData); err != nil {
		if os.IsNotExist(err) { // Does not exist, so create it
			if err = os.MkdirAll(dbf.PathData, 0775); err != nil {
				fmt.Fprintf(os.Stderr, "Can not create data folder. Error: %s\n", err) // Error in creating
				panic(1)
			}
		} else {
			fmt.Fprintf(os.Stderr, "Can not access data folder. Error: %s\n", err) // Error in accessing
			panic(1)
		}
	}

	dbf.InitLog()
	dbf.InitConfig()
}

func main() {
	defer deferPanic()

	term.Clear()
	initialize()
	term.Clear()

	timer = 0
	dbf.ResetTimer = false

	var enterPressed bool = false
	go func(enterPressed *bool) {
		for {
			fmt.Scanln()
			*enterPressed = true
		}
	}(&enterPressed)

	for { // Infinite loop
		fmt.Println("Checking for new task from server...")

		dbf.GetTask()

		fmt.Println("Done\n")

		timeout := getTimer()
		enterPressed = false
		showRemainingTime(timeout)
		timeout--

		ticker := time.NewTicker(time.Second)
		for range ticker.C {
			showRemainingTime(timeout)

			if enterPressed {
				ticker.Stop()
				dbf.ResetTimer = true
				enterPressed = false
				break
			}

			if timeout == 0 {
				ticker.Stop()
				break
			}

			timeout--
		}

		term.Clear()
	}
}

func getTimer() uint {
	if dbf.ResetTimer {
		timer = 1
		dbf.ResetTimer = false
	} else {
		timer++
	}

	if timer > 150 {
		timer = 150
	}

	return timer * 4
}

func showRemainingTime(timeout uint) {
	switch {
	case timeout > 119:
		fmt.Printf("\rPerform next check in %d minutes... (Press enter to check now) ", timeout/60)
	case timeout > 59:
		fmt.Printf("\rPerform next check in %d minute... (Press enter to check now) ", 1)
	case timeout > 1:
		fmt.Printf("\rPerform next check in %d seconds... (Press enter to check now) ", timeout)
	default:
		fmt.Printf("\rPerform next check in %d second... (Press enter to check now) ", 1)
	}
}
<commit_msg>When timeout reaches to 2 minutes then wait for a random timeout between 2 and 5 minutes<commit_after>package main

import (
	"fmt"
	"github.com/d-bf/client/dbf"
	"github.com/d-bf/client/term"
	"math/rand"
	"os"
	"path/filepath"
	"time"
)

var timer uint

func deferPanic() {
	if panicVal := recover(); panicVal != nil { // Recovering from panic
		if exitCode, ok := panicVal.(int); ok { // Panic value is integer
			os.Exit(exitCode) // Exit with the integer panic value
		}
	}
}

func initialize() {
	fmt.Println("Initializing...")

	// Set data path
	pathData, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err == nil {
		dbf.PathData = pathData + string(os.PathSeparator) + "dbf-data" + string(os.PathSeparator)
	} else {
		fmt.Fprintf(os.Stderr, "Can not set current path. Error: %s\n", err)
		panic(1)
	}

	// Check data folder
	if _, err = os.Stat(dbf.PathData); err != nil {
		if os.IsNotExist(err) { // Does not exist, so create it
			if err = os.MkdirAll(dbf.PathData, 0775); err != nil {
				fmt.Fprintf(os.Stderr, "Can not create data folder. Error: %s\n", err) // Error in creating
				panic(1)
			}
		} else {
			fmt.Fprintf(os.Stderr, "Can not access data folder. Error: %s\n", err) // Error in accessing
			panic(1)
		}
	}

	dbf.InitLog()
	dbf.InitConfig()
}

func main() {
	defer deferPanic()

	term.Clear()
	initialize()
	term.Clear()

	timer = 0
	dbf.ResetTimer = false

	var enterPressed bool = false
	go func(enterPressed *bool) {
		for {
			fmt.Scanln()
			*enterPressed = true
		}
	}(&enterPressed)

	for { // Infinite loop
		fmt.Println("Checking for new task from server...")

		dbf.GetTask()

		fmt.Println("Done\n")

		timeout := getTimer()
		enterPressed = false
		showRemainingTime(timeout)
		timeout--

		ticker := time.NewTicker(time.Second)
		for range ticker.C {
			showRemainingTime(timeout)

			if enterPressed {
				ticker.Stop()
				dbf.ResetTimer = true
				enterPressed = false
				break
			}

			if timeout == 0 {
				ticker.Stop()
				break
			}

			timeout--
		}

		term.Clear()
	}
}

func getTimer() uint {
	if dbf.ResetTimer {
		timer = 1
		dbf.ResetTimer = false
	} else {
		timer++
	}

	if timer > 29 { // 30 * 4 = 2 minutes
		timer = uint(random(30, 75)) // * 4 = (2 minutes, 5 minutes)
	}

	return timer * 4
}

func random(min, max int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(max-min) + min
}

func showRemainingTime(timeout uint) {
	switch {
	case timeout > 119:
		fmt.Printf("\rPerform next check in %d minutes... (Press enter to check now) ", timeout/60)
	case timeout > 59:
		fmt.Printf("\rPerform next check in %d minute... (Press enter to check now) ", 1)
	case timeout > 1:
		fmt.Printf("\rPerform next check in %d seconds... (Press enter to check now) ", timeout)
	default:
		fmt.Printf("\rPerform next check in %d second... (Press enter to check now) ", 1)
	}
}
<|endoftext|>


<commit_before>/*
   Copyright 2014 GoPivotal (UK) Limited.

   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 syscall-linux wraps the standard syscall package for Linux.
*/
package syscall_linux

import (
	"github.com/cf-guardian/guardian/gerror"
	syscall "github.com/cf-guardian/guardian/kernel/syscall"
	"os"
	trueSyscall "syscall"
)

// ImplErrorId is used for error ids relating to the implementation of this package.
type ImplErrorId int

const (
	ErrNotRoot ImplErrorId = iota // root is required to create a SyscallFS
)

type syscallWrapper struct {
}

/*
	Constructs a new SyscallFS instance and returns it providing the effective user id
	is root. Otherwise return an error.
*/
func NewFS() (syscall.SyscallFS, error) {
	euid := os.Geteuid()
	if euid != 0 {
		return nil, gerror.Newf(ErrNotRoot, "Effective user id %d is not root", euid)
	}
	return &syscallWrapper{}, nil
}

func (_ *syscallWrapper) BindMountReadWrite(source string, mountPoint string) error {
	return trueSyscall.Mount(source, mountPoint, "", trueSyscall.MS_BIND, "")
}

func (_ *syscallWrapper) BindMountReadOnly(source string, mountPoint string) error {

	return trueSyscall.Mount(source, mountPoint, "", trueSyscall.MS_BIND|trueSyscall.MS_RDONLY, "")
}

func (_ *syscallWrapper) Unmount(mountPoint string) error {
	return trueSyscall.Unmount(mountPoint, 0)
}
<commit_msg>Rebind bind mount read-only<commit_after>/*
   Copyright 2014 GoPivotal (UK) Limited.

   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 syscall-linux wraps the standard syscall package for Linux.
*/
package syscall_linux

import (
	"github.com/cf-guardian/guardian/gerror"
	syscall "github.com/cf-guardian/guardian/kernel/syscall"
	"os"
	trueSyscall "syscall"
)

// ImplErrorId is used for error ids relating to the implementation of this package.
type ImplErrorId int

const (
	ErrNotRoot ImplErrorId = iota // root is required to create a SyscallFS
)

type syscallWrapper struct {
}

/*
	Constructs a new SyscallFS instance and returns it providing the effective user id
	is root. Otherwise return an error.
*/
func NewFS() (syscall.SyscallFS, error) {
	euid := os.Geteuid()
	if euid != 0 {
		return nil, gerror.Newf(ErrNotRoot, "Effective user id %d is not root", euid)
	}
	return &syscallWrapper{}, nil
}

func (_ *syscallWrapper) BindMountReadWrite(source string, mountPoint string) error {
	return trueSyscall.Mount(source, mountPoint, "", trueSyscall.MS_BIND, "")
}

func (sc *syscallWrapper) BindMountReadOnly(source string, mountPoint string) error {
	// On kernels earlier than 2.6.26, a read-only bind mount must be formed by remounting a read-write bind mount read-only.
	err := sc.BindMountReadWrite(source, mountPoint)
	if err != nil {
		return err
	}
	return trueSyscall.Mount(source, mountPoint, "", trueSyscall.MS_BIND|trueSyscall.MS_REMOUNT|trueSyscall.MS_RDONLY, "")
}

func (_ *syscallWrapper) Unmount(mountPoint string) error {
	return trueSyscall.Unmount(mountPoint, 0)
}
<|endoftext|>


<commit_before>/*
 *    Copyright (C) 2016 Gonzalo Izquierdo
 *                  2017 Christian Muehlhaeuser
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as published
 *    by the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    Authors:
 *      Gonzalo Izquierdo <lalotone@gmail.com>
 *      Christian Muehlhaeuser <muesli@gmail.com>
 */

// Package telegrambee is a Bee that can connect to Telegram.
package telegrambee

import (
	"io/ioutil"
	"os"
	"strconv"
	"strings"

	telegram "gopkg.in/telegram-bot-api.v4"

	"github.com/muesli/beehive/bees"
)

// TelegramBee is a Bee that can connect to Telegram.
type TelegramBee struct {
	bees.Bee

	// Telegram bot API Key
	apiKey string
	// Bot API client
	bot *telegram.BotAPI
}

// Action triggers the action passed to it.
func (mod *TelegramBee) Action(action bees.Action) []bees.Placeholder {
	outs := []bees.Placeholder{}

	switch action.Name {
	case "send":
		chatID := ""
		text := ""
		action.Options.Bind("chat_id", &chatID)
		action.Options.Bind("text", &text)

		cid, err := strconv.Atoi(chatID)
		if err != nil {
			panic("Invalid telegram chat ID")
		}

		msg := telegram.NewMessage(int64(cid), text)
		_, err = mod.bot.Send(msg)
		if err != nil {
			mod.Logf("Error sending message %v", err)
		}
	}

	return outs
}

// Run executes the Bee's event loop.
func (mod *TelegramBee) Run(eventChan chan bees.Event) {
	u := telegram.NewUpdate(0)
	u.Timeout = 60

	updates, err := mod.bot.GetUpdatesChan(u)
	if err != nil {
		panic(err)
	}

	for update := range updates {
		if update.Message == nil || update.Message.Text == "" {
			continue
		}

		ev := bees.Event{
			Bee:  mod.Name(),
			Name: "message",
			Options: []bees.Placeholder{
				{
					Name:  "text",
					Type:  "string",
					Value: update.Message.Text,
				},
				{
					Name:  "chat_id",
					Type:  "string",
					Value: strconv.FormatInt(update.Message.Chat.ID, 10),
				},
				{
					Name:  "user_id",
					Type:  "string",
					Value: strconv.Itoa(update.Message.From.ID),
				},
			},
		}
		eventChan <- ev
	}
}

// Stop stops the running Bee.
func (mod *TelegramBee) Stop() {
	mod.Logln("Stopping the Telegram bee")
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *TelegramBee) ReloadOptions(options bees.BeeOptions) {
	mod.SetOptions(options)

	apiKey := getAPIKey(&options)
	bot, err := telegram.NewBotAPI(apiKey)
	if err != nil {
		panic("Authorization failed, make sure the Telegram API key is correct")
	}
	mod.Logf("TELEGRAM: Authorized on account %s", bot.Self.UserName)

	mod.apiKey = apiKey
	mod.bot = bot
}

// Gets the Bot's API key from a file, the recipe config or the
// TELEGRAM_API_KEY environment variable.
func getAPIKey(options *bees.BeeOptions) string {
	var apiKey string
	options.Bind("api_key", &apiKey)

	if strings.HasPrefix(apiKey, "file://") {
		buf, err := ioutil.ReadFile(strings.TrimPrefix(apiKey, "file://"))
		if err != nil {
			panic("Error reading API key file " + apiKey)
		}
		apiKey = string(buf)
	}

	if strings.HasPrefix(apiKey, "env://") {
		buf := strings.TrimPrefix(apiKey, "env://")
		apiKey = os.Getenv(string(buf))
	}

	return strings.TrimSpace(apiKey)
}
<commit_msg>Don't block in TelegramBee so it can be gracefully stopped<commit_after>/*
 *    Copyright (C) 2016 Gonzalo Izquierdo
 *                  2017 Christian Muehlhaeuser
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as published
 *    by the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    Authors:
 *      Gonzalo Izquierdo <lalotone@gmail.com>
 *      Christian Muehlhaeuser <muesli@gmail.com>
 */

// Package telegrambee is a Bee that can connect to Telegram.
package telegrambee

import (
	"io/ioutil"
	"os"
	"strconv"
	"strings"

	telegram "gopkg.in/telegram-bot-api.v4"

	"github.com/muesli/beehive/bees"
)

// TelegramBee is a Bee that can connect to Telegram.
type TelegramBee struct {
	bees.Bee

	// Telegram bot API Key
	apiKey string
	// Bot API client
	bot *telegram.BotAPI
}

// Action triggers the action passed to it.
func (mod *TelegramBee) Action(action bees.Action) []bees.Placeholder {
	outs := []bees.Placeholder{}

	switch action.Name {
	case "send":
		chatID := ""
		text := ""
		action.Options.Bind("chat_id", &chatID)
		action.Options.Bind("text", &text)

		cid, err := strconv.Atoi(chatID)
		if err != nil {
			panic("Invalid telegram chat ID")
		}

		msg := telegram.NewMessage(int64(cid), text)
		_, err = mod.bot.Send(msg)
		if err != nil {
			mod.Logf("Error sending message %v", err)
		}
	}

	return outs
}

// Run executes the Bee's event loop.
func (mod *TelegramBee) Run(eventChan chan bees.Event) {
	var err error
	mod.bot, err = telegram.NewBotAPI(mod.apiKey)
	if err != nil {
		mod.LogErrorf("Authorization failed, make sure the Telegram API key is correct: %s", err)
		return
	}
	mod.Logf("Authorized on account %s", mod.bot.Self.UserName)

	u := telegram.NewUpdate(0)
	u.Timeout = 60

	updates, err := mod.bot.GetUpdatesChan(u)
	if err != nil {
		panic(err)
	}

	for {
		select {
		case <-mod.SigChan:
			return
		case update := <-updates:
			if update.Message == nil || update.Message.Text == "" {
				continue
			}

			ev := bees.Event{
				Bee:  mod.Name(),
				Name: "message",
				Options: []bees.Placeholder{
					{
						Name:  "text",
						Type:  "string",
						Value: update.Message.Text,
					},
					{
						Name:  "chat_id",
						Type:  "string",
						Value: strconv.FormatInt(update.Message.Chat.ID, 10),
					},
					{
						Name:  "user_id",
						Type:  "string",
						Value: strconv.Itoa(update.Message.From.ID),
					},
				},
			}
			eventChan <- ev
		}
	}
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *TelegramBee) ReloadOptions(options bees.BeeOptions) {
	mod.SetOptions(options)

	apiKey := getAPIKey(&options)
	mod.apiKey = apiKey
}

// Gets the Bot's API key from a file, the recipe config or the
// TELEGRAM_API_KEY environment variable.
func getAPIKey(options *bees.BeeOptions) string {
	var apiKey string
	options.Bind("api_key", &apiKey)

	if strings.HasPrefix(apiKey, "file://") {
		buf, err := ioutil.ReadFile(strings.TrimPrefix(apiKey, "file://"))
		if err != nil {
			panic("Error reading API key file " + apiKey)
		}
		apiKey = string(buf)
	}

	if strings.HasPrefix(apiKey, "env://") {
		buf := strings.TrimPrefix(apiKey, "env://")
		apiKey = os.Getenv(string(buf))
	}

	return strings.TrimSpace(apiKey)
}
<|endoftext|>


<commit_before>package config

import (
	"flag"
	"fmt"
	"gondola/log"
	"gondola/signal"
	"gondola/util"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"strings"
)

var (
	defaultFilename = util.RelativePath("conf/current.conf")
	configName      *string
)

type fieldValue struct {
	Value reflect.Value
	Tag   reflect.StructTag
}

type fieldMap map[string]*fieldValue

func (f fieldMap) Append(name string, value reflect.Value, tag reflect.StructTag) error {
	if _, ok := f[name]; ok {
		return fmt.Errorf("duplicate field name %q", name)
	}
	f[name] = &fieldValue{value, tag}
	return nil
}

type varMap map[string]interface{}

// SetDefaultFilename changes the default filename used by Parse().
func SetDefaultFilename(name string) {
	defaultFilename = name
}

// DefaultFilename returns the default config filename used by Parse().
// It might changed by calling SetDefaultFilename() or overriden using
// the -config command line flag (the latter, of present, takes precendence).
// The initial value is the path conf/current.conf relative to the application
// binary.
func DefaultFilename() string {
	return defaultFilename
}

// Filename returns the current filename used by Parse(). If the -config command
// line flag was provided, it returns its value. Otherwise, it returns
// DefaultFilename().
func Filename() string {
	if configName == nil {
		return defaultFilename
	}
	return *configName
}

func fileParameterName(name string) string {
	return util.CamelCaseToLower(name, "_")
}

func flagParameterName(name string) string {
	return util.CamelCaseToLower(name, "-")
}

func parseValue(v reflect.Value, raw string) error {
	switch v.Type().Kind() {
	case reflect.Bool:
		value, err := strconv.ParseBool(raw)
		if err != nil {
			return err
		}
		v.SetBool(value)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		value, err := strconv.ParseInt(raw, 10, 64)
		if err != nil {
			return err
		}
		v.SetInt(int64(value))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		value, err := strconv.ParseUint(raw, 10, 64)
		if err != nil {
			return err
		}
		v.SetUint(uint64(value))
	case reflect.Float32, reflect.Float64:
		value, err := strconv.ParseFloat(raw, 64)
		if err != nil {
			return err
		}
		v.SetFloat(value)
	case reflect.String:
		v.SetString(raw)
	default:
		return fmt.Errorf("can't parse values of type %q", v.Type().Name())
	}
	return nil
}

// ParseFile parses the given config file into the given config
// struct. No signal is emitted. Look at the documentation of
// Parse() for information on the supported types as well as
// the name mangling performed in the struct fields to convert
// them to config file keys.
func ParseFile(filename string, config interface{}) error {
	fields, err := configFields(config)
	if err != nil {
		return err
	}
	return parseFile(filename, fields)
}

// ParseReader parses the config rom the given io.Reader into
// the given config struct. No signal is emitted. Look at the documentation
// of Parse() for information on the supported types as well as
// the name mangling performed in the struct fields to convert
// them to config file keys.
func ParseReader(r io.Reader, config interface{}) error {
	fields, err := configFields(config)
	if err != nil {
		return err
	}
	return parseReader(r, fields)
}

func parseFile(filename string, fields fieldMap) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	return parseReader(f, fields)
}

func parseReader(r io.Reader, fields fieldMap) error {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}
	/* Copy strings to a map */
	values := make(map[string]string)
	for _, line := range strings.Split(string(b), "\n") {
		trimmed := strings.TrimSpace(line)
		if trimmed != "" {
			parts := strings.SplitN(trimmed, "=", 2)
			if len(parts) == 2 {
				key := strings.TrimSpace(parts[0])
				value := strings.TrimSpace(parts[1])
				values[key] = value
			}
		}
	}
	/* Now iterate over the fields and copy from the map */
	for k, v := range fields {
		name := fileParameterName(k)
		if raw, ok := values[name]; ok && raw != "" {
			err := parseValue(v.Value, raw)
			if err != nil {
				return fmt.Errorf("error parsing config file field %q (struct field %q): %s", name, k, err)
			}
		}
	}
	return nil
}

func setupFlags(fields fieldMap) (varMap, error) {
	m := make(varMap)
	for k, v := range fields {
		name := flagParameterName(k)
		help := v.Tag.Get("help")
		var p interface{}
		val := v.Value
		switch val.Type().Kind() {
		case reflect.Bool:
			p = flag.Bool(name, val.Bool(), help)
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
			p = flag.Int(name, int(val.Int()), help)
		case reflect.Int64:
			p = flag.Int64(name, val.Int(), help)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
			p = flag.Uint(name, uint(val.Uint()), help)
		case reflect.Uint64:
			p = flag.Uint64(name, val.Uint(), help)
		case reflect.Float32, reflect.Float64:
			p = flag.Float64(name, val.Float(), help)
		case reflect.String:
			p = flag.String(name, val.String(), help)
		default:
			return nil, fmt.Errorf("invalid type in config %s (field %s)", val.Type().Name(), k)
		}
		m[name] = p
	}
	return m, nil
}

func copyFlagValues(fields fieldMap, values varMap) error {
	/* Copy only flags which have been set */
	setFlags := make(map[string]bool)
	flag.Visit(func(f *flag.Flag) {
		setFlags[f.Name] = true
	})
	for k, v := range fields {
		name := flagParameterName(k)
		if !setFlags[name] {
			continue
		}
		val := v.Value
		switch val.Type().Kind() {
		case reflect.Bool:
			value := *(values[name].(*bool))
			val.SetBool(value)
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
			value := *(values[name].(*int))
			val.SetInt(int64(value))
		case reflect.Int64:
			value := *(values[name].(*int64))
			val.SetInt(value)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
			value := *(values[name].(*uint))
			val.SetUint(uint64(value))
		case reflect.Uint64:
			value := *(values[name].(*uint64))
			val.SetUint(value)
		case reflect.Float32, reflect.Float64:
			value := *(values[name].(*float64))
			val.SetFloat(value)
		case reflect.String:
			value := *(values[name].(*string))
			val.SetString(value)
		default:
			return fmt.Errorf("invalid type in config %q (field %q)", val.Type().Name(), k)
		}
	}
	return nil
}

func configFields(config interface{}) (fieldMap, error) {
	value := reflect.ValueOf(config)
	for value.Kind() == reflect.Ptr {
		if value.IsNil() {
			value.Set(reflect.New(value.Type().Elem()))
		}
		value = value.Elem()
	}
	if !value.CanAddr() {
		return nil, fmt.Errorf("config must be a pointer to a struct (it's %T)", config)
	}
	fields := make(fieldMap)
	valueType := value.Type()
	for ii := 0; ii < value.NumField(); ii++ {
		field := value.Field(ii)
		if field.Type().Kind() == reflect.Struct {
			subfields, err := configFields(field.Addr().Interface())
			if err != nil {
				return nil, err
			}
			for k, v := range subfields {
				err := fields.Append(k, v.Value, v.Tag)
				if err != nil {
					return nil, err
				}
			}
		} else {
			sfield := valueType.Field(ii)
			if def := sfield.Tag.Get("default"); def != "" {
				err := parseValue(field, def)
				if err != nil {
					return nil, fmt.Errorf("error parsing default value for field %q: %s", sfield.Name, err)
				}
			}
			err := fields.Append(sfield.Name, field, sfield.Tag)
			if err != nil {
				return nil, err
			}
		}
	}
	return fields, nil
}

// Parse parses the application configuration into the given config struct. If
// the configuration is parsed successfully, the signal signal.CONFIGURED is
// emitted with the given config as its object (which sets the parameters
// in gondola/defaults). Check the documentation on the gondola/signal package
// to learn more about Gondola's signals.
//
// Supported types include bool, string, u?int(|8|6|32|62) and float(32|64). If
// any config field type is not supported, an error is returned. Additionally,
// two struct tags are taken into account. The "help" tag is used when to provide
// a help string to the user when defining command like flags, while the "default"
// tag is used to provide a default value for the field in case it hasn't been
// provided as a config key nor a command line flag.
//
// The parsing process starts by reading the config file returned by Filename()
// (which might be overriden by the -config command line flag), and then parses
// any flags provided in the command line. This means any value in the config
// file might be overriden by a command line flag.
//
// Go's idiomatic camel-cased struct field names are mangled into lowercase words
// to produce the flag names and config fields. e.g. a field named "FooBar" will
// produce a "-foo-bar" flag and a "foo_bar" config key. Embedded struct are
// flattened, as if their fields were part of the container struct. Finally, while
// not mandatory, is very recommended that your config struct embeds config.Config,
// so the standard parameters for Gondola applications are already defined for you.
// e.g.
//
//  var MyConfig struct {
//	config.Config
//	MyStringValue string
//	MyIntValue int `help:"Some int used for something" default:"42"`
//  }
//
//  func init() {
//	config.MustParse(&MyConfig)
//  }
//  // Besides the Gondola's standard flags and keys, this config would define
//  // the flags -my-string-value and -my-int-value as well as the config file keys
//  // my_string_value and my_int_value.
//
func Parse(config interface{}) error {
	configName = flag.String("config", defaultFilename, "Config file name")
	fields, err := configFields(config)
	if err != nil {
		return err
	}
	/* Setup flags before calling flag.Parse() */
	flagValues, err := setupFlags(fields)
	if err != nil {
		return err
	}
	/* Now parse the flags */
	flag.Parse()
	/* Read config file first */
	if fn := Filename(); fn != "" {
		if err := parseFile(fn, fields); err != nil {
			return err
		}
	}
	/* Command line overrides config file */
	if err := copyFlagValues(fields, flagValues); err != nil {
		return err
	}
	signal.Emit(signal.CONFIGURED, config)
	return nil
}

// MustParse works like Parse, but panics if there's an error.
func MustParse(config interface{}) {
	err := Parse(config)
	if err != nil {
		log.Panicf("error parsing config: %s", err)
	}
}
<commit_msg>Change example to make it more clear how names with caps are handled<commit_after>package config

import (
	"flag"
	"fmt"
	"gondola/log"
	"gondola/signal"
	"gondola/util"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"strings"
)

var (
	defaultFilename = util.RelativePath("conf/current.conf")
	configName      *string
)

type fieldValue struct {
	Value reflect.Value
	Tag   reflect.StructTag
}

type fieldMap map[string]*fieldValue

func (f fieldMap) Append(name string, value reflect.Value, tag reflect.StructTag) error {
	if _, ok := f[name]; ok {
		return fmt.Errorf("duplicate field name %q", name)
	}
	f[name] = &fieldValue{value, tag}
	return nil
}

type varMap map[string]interface{}

// SetDefaultFilename changes the default filename used by Parse().
func SetDefaultFilename(name string) {
	defaultFilename = name
}

// DefaultFilename returns the default config filename used by Parse().
// It might changed by calling SetDefaultFilename() or overriden using
// the -config command line flag (the latter, of present, takes precendence).
// The initial value is the path conf/current.conf relative to the application
// binary.
func DefaultFilename() string {
	return defaultFilename
}

// Filename returns the current filename used by Parse(). If the -config command
// line flag was provided, it returns its value. Otherwise, it returns
// DefaultFilename().
func Filename() string {
	if configName == nil {
		return defaultFilename
	}
	return *configName
}

func fileParameterName(name string) string {
	return util.CamelCaseToLower(name, "_")
}

func flagParameterName(name string) string {
	return util.CamelCaseToLower(name, "-")
}

func parseValue(v reflect.Value, raw string) error {
	switch v.Type().Kind() {
	case reflect.Bool:
		value, err := strconv.ParseBool(raw)
		if err != nil {
			return err
		}
		v.SetBool(value)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		value, err := strconv.ParseInt(raw, 10, 64)
		if err != nil {
			return err
		}
		v.SetInt(int64(value))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		value, err := strconv.ParseUint(raw, 10, 64)
		if err != nil {
			return err
		}
		v.SetUint(uint64(value))
	case reflect.Float32, reflect.Float64:
		value, err := strconv.ParseFloat(raw, 64)
		if err != nil {
			return err
		}
		v.SetFloat(value)
	case reflect.String:
		v.SetString(raw)
	default:
		return fmt.Errorf("can't parse values of type %q", v.Type().Name())
	}
	return nil
}

// ParseFile parses the given config file into the given config
// struct. No signal is emitted. Look at the documentation of
// Parse() for information on the supported types as well as
// the name mangling performed in the struct fields to convert
// them to config file keys.
func ParseFile(filename string, config interface{}) error {
	fields, err := configFields(config)
	if err != nil {
		return err
	}
	return parseFile(filename, fields)
}

// ParseReader parses the config rom the given io.Reader into
// the given config struct. No signal is emitted. Look at the documentation
// of Parse() for information on the supported types as well as
// the name mangling performed in the struct fields to convert
// them to config file keys.
func ParseReader(r io.Reader, config interface{}) error {
	fields, err := configFields(config)
	if err != nil {
		return err
	}
	return parseReader(r, fields)
}

func parseFile(filename string, fields fieldMap) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	return parseReader(f, fields)
}

func parseReader(r io.Reader, fields fieldMap) error {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}
	/* Copy strings to a map */
	values := make(map[string]string)
	for _, line := range strings.Split(string(b), "\n") {
		trimmed := strings.TrimSpace(line)
		if trimmed != "" {
			parts := strings.SplitN(trimmed, "=", 2)
			if len(parts) == 2 {
				key := strings.TrimSpace(parts[0])
				value := strings.TrimSpace(parts[1])
				values[key] = value
			}
		}
	}
	/* Now iterate over the fields and copy from the map */
	for k, v := range fields {
		name := fileParameterName(k)
		if raw, ok := values[name]; ok && raw != "" {
			err := parseValue(v.Value, raw)
			if err != nil {
				return fmt.Errorf("error parsing config file field %q (struct field %q): %s", name, k, err)
			}
		}
	}
	return nil
}

func setupFlags(fields fieldMap) (varMap, error) {
	m := make(varMap)
	for k, v := range fields {
		name := flagParameterName(k)
		help := v.Tag.Get("help")
		var p interface{}
		val := v.Value
		switch val.Type().Kind() {
		case reflect.Bool:
			p = flag.Bool(name, val.Bool(), help)
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
			p = flag.Int(name, int(val.Int()), help)
		case reflect.Int64:
			p = flag.Int64(name, val.Int(), help)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
			p = flag.Uint(name, uint(val.Uint()), help)
		case reflect.Uint64:
			p = flag.Uint64(name, val.Uint(), help)
		case reflect.Float32, reflect.Float64:
			p = flag.Float64(name, val.Float(), help)
		case reflect.String:
			p = flag.String(name, val.String(), help)
		default:
			return nil, fmt.Errorf("invalid type in config %s (field %s)", val.Type().Name(), k)
		}
		m[name] = p
	}
	return m, nil
}

func copyFlagValues(fields fieldMap, values varMap) error {
	/* Copy only flags which have been set */
	setFlags := make(map[string]bool)
	flag.Visit(func(f *flag.Flag) {
		setFlags[f.Name] = true
	})
	for k, v := range fields {
		name := flagParameterName(k)
		if !setFlags[name] {
			continue
		}
		val := v.Value
		switch val.Type().Kind() {
		case reflect.Bool:
			value := *(values[name].(*bool))
			val.SetBool(value)
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
			value := *(values[name].(*int))
			val.SetInt(int64(value))
		case reflect.Int64:
			value := *(values[name].(*int64))
			val.SetInt(value)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
			value := *(values[name].(*uint))
			val.SetUint(uint64(value))
		case reflect.Uint64:
			value := *(values[name].(*uint64))
			val.SetUint(value)
		case reflect.Float32, reflect.Float64:
			value := *(values[name].(*float64))
			val.SetFloat(value)
		case reflect.String:
			value := *(values[name].(*string))
			val.SetString(value)
		default:
			return fmt.Errorf("invalid type in config %q (field %q)", val.Type().Name(), k)
		}
	}
	return nil
}

func configFields(config interface{}) (fieldMap, error) {
	value := reflect.ValueOf(config)
	for value.Kind() == reflect.Ptr {
		if value.IsNil() {
			value.Set(reflect.New(value.Type().Elem()))
		}
		value = value.Elem()
	}
	if !value.CanAddr() {
		return nil, fmt.Errorf("config must be a pointer to a struct (it's %T)", config)
	}
	fields := make(fieldMap)
	valueType := value.Type()
	for ii := 0; ii < value.NumField(); ii++ {
		field := value.Field(ii)
		if field.Type().Kind() == reflect.Struct {
			subfields, err := configFields(field.Addr().Interface())
			if err != nil {
				return nil, err
			}
			for k, v := range subfields {
				err := fields.Append(k, v.Value, v.Tag)
				if err != nil {
					return nil, err
				}
			}
		} else {
			sfield := valueType.Field(ii)
			if def := sfield.Tag.Get("default"); def != "" {
				err := parseValue(field, def)
				if err != nil {
					return nil, fmt.Errorf("error parsing default value for field %q: %s", sfield.Name, err)
				}
			}
			err := fields.Append(sfield.Name, field, sfield.Tag)
			if err != nil {
				return nil, err
			}
		}
	}
	return fields, nil
}

// Parse parses the application configuration into the given config struct. If
// the configuration is parsed successfully, the signal signal.CONFIGURED is
// emitted with the given config as its object (which sets the parameters
// in gondola/defaults). Check the documentation on the gondola/signal package
// to learn more about Gondola's signals.
//
// Supported types include bool, string, u?int(|8|6|32|62) and float(32|64). If
// any config field type is not supported, an error is returned. Additionally,
// two struct tags are taken into account. The "help" tag is used when to provide
// a help string to the user when defining command like flags, while the "default"
// tag is used to provide a default value for the field in case it hasn't been
// provided as a config key nor a command line flag.
//
// The parsing process starts by reading the config file returned by Filename()
// (which might be overriden by the -config command line flag), and then parses
// any flags provided in the command line. This means any value in the config
// file might be overriden by a command line flag.
//
// Go's idiomatic camel-cased struct field names are mangled into lowercase words
// to produce the flag names and config fields. e.g. a field named "FooBar" will
// produce a "-foo-bar" flag and a "foo_bar" config key. Embedded struct are
// flattened, as if their fields were part of the container struct. Finally, while
// not mandatory, is very recommended that your config struct embeds config.Config,
// so the standard parameters for Gondola applications are already defined for you.
// e.g.
//
//  var MyConfig struct {
//	config.Config
//	MyStringValue	string
//	MyINTValue	int `help:"Some int used for something" default:"42"`
//  }
//
//  func init() {
//	config.MustParse(&MyConfig)
//  }
//  // Besides the Gondola's standard flags and keys, this config would define
//  // the flags -my-string-value and -my-int-value as well as the config file keys
//  // my_string_value and my_int_value.
//
func Parse(config interface{}) error {
	configName = flag.String("config", defaultFilename, "Config file name")
	fields, err := configFields(config)
	if err != nil {
		return err
	}
	/* Setup flags before calling flag.Parse() */
	flagValues, err := setupFlags(fields)
	if err != nil {
		return err
	}
	/* Now parse the flags */
	flag.Parse()
	/* Read config file first */
	if fn := Filename(); fn != "" {
		if err := parseFile(fn, fields); err != nil {
			return err
		}
	}
	/* Command line overrides config file */
	if err := copyFlagValues(fields, flagValues); err != nil {
		return err
	}
	signal.Emit(signal.CONFIGURED, config)
	return nil
}

// MustParse works like Parse, but panics if there's an error.
func MustParse(config interface{}) {
	err := Parse(config)
	if err != nil {
		log.Panicf("error parsing config: %s", err)
	}
}
<|endoftext|>


<commit_before>package conio

import (
	"bytes"
	"strings"
)

const NULQUOTE = '\000'

func QuotedWordCutter(reader *strings.Reader) (string, bool) {
	var buffer bytes.Buffer
	for {
		if reader.Len() <= 0 {
			return "", false
		}
		ch, _, _ := reader.ReadRune()
		if ch != ' ' {
			reader.UnreadRune()
			break
		}
	}
	quote := NULQUOTE
	yenCount := 0
	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		if yenCount%2 == 0 {
			if quote == NULQUOTE && (ch == '"' || ch == '\'') {
				quote = ch
			} else if quote != NULQUOTE && ch == quote {
				quote = NULQUOTE
			}
		}
		if ch == ' ' && quote == NULQUOTE {
			break
		}
		if ch == '\\' {
			yenCount++
		} else {
			yenCount = 0
		}
		buffer.WriteRune(ch)
	}
	return buffer.String(), true
}

// Split s with SPACES not enclosing with double-quotations.
func SplitQ(line string) []string {
	args := make([]string, 0, 10)
	reader := strings.NewReader(line)
	for reader.Len() > 0 {
		word, ok := QuotedWordCutter(reader)
		if ok {
			args = append(args, word)
		}
	}
	return args
}

func QuotedFirstWord(line string) string {
	reader := strings.NewReader(line)
	str, _ := QuotedWordCutter(reader)
	return str
}
<commit_msg>Improved code: hide a private function of conio-package<commit_after>package conio

import (
	"bytes"
	"strings"
)

const NULQUOTE = '\000'

func quotedWordCutter(reader *strings.Reader) (string, bool) {
	var buffer bytes.Buffer
	for {
		if reader.Len() <= 0 {
			return "", false
		}
		ch, _, _ := reader.ReadRune()
		if ch != ' ' {
			reader.UnreadRune()
			break
		}
	}
	quote := NULQUOTE
	yenCount := 0
	for reader.Len() > 0 {
		ch, _, _ := reader.ReadRune()
		if yenCount%2 == 0 {
			if quote == NULQUOTE && (ch == '"' || ch == '\'') {
				quote = ch
			} else if quote != NULQUOTE && ch == quote {
				quote = NULQUOTE
			}
		}
		if ch == ' ' && quote == NULQUOTE {
			break
		}
		if ch == '\\' {
			yenCount++
		} else {
			yenCount = 0
		}
		buffer.WriteRune(ch)
	}
	return buffer.String(), true
}

// Split s with SPACES not enclosing with double-quotations.
func SplitQ(line string) []string {
	args := make([]string, 0, 10)
	reader := strings.NewReader(line)
	for reader.Len() > 0 {
		word, ok := quotedWordCutter(reader)
		if ok {
			args = append(args, word)
		}
	}
	return args
}

func QuotedFirstWord(line string) string {
	reader := strings.NewReader(line)
	str, _ := quotedWordCutter(reader)
	return str
}
<|endoftext|>


<commit_before><commit_msg>test binary<commit_after><|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/shiena/ansicolor"

	"../alias"
	"../completion"
	"../conio"
	"../dos"
	"../interpreter"
	"../lua"
)

const alias_prefix = "nyagos.alias."

func cmdSetAlias(L lua.Lua) int {
	name, nameErr := L.ToString(-2)
	if nameErr != nil {
		return L.Push(nil, nameErr)
	}
	key := strings.ToLower(name)
	switch L.GetType(-1) {
	case lua.LUA_TSTRING:
		value, err := L.ToString(-1)
		regkey := alias_prefix + key
		L.SetField(lua.LUA_REGISTRYINDEX, regkey)
		if err == nil {
			alias.Table[key] = alias.New(value)
		} else {
			return L.Push(nil, err)
		}
	case lua.LUA_TFUNCTION:
		regkey := alias_prefix + key
		L.SetField(lua.LUA_REGISTRYINDEX, regkey)
		alias.Table[key] = LuaFunction{L, regkey}
	}
	return L.Push(true)
}

func cmdGetAlias(L lua.Lua) int {
	name, nameErr := L.ToString(1)
	if nameErr != nil {
		return L.Push(nil, nameErr)
	}
	regkey := alias_prefix + strings.ToLower(name)
	L.GetField(lua.LUA_REGISTRYINDEX, regkey)
	return 1
}

func cmdSetEnv(L lua.Lua) int {
	name, nameErr := L.ToString(-2)
	if nameErr != nil {
		return L.Push(nil, nameErr)
	}
	value, valueErr := L.ToString(-1)
	if valueErr != nil {
		return L.Push(nil, valueErr)
	}
	if len(value) > 0 {
		os.Setenv(name, value)
	} else {
		os.Unsetenv(name)
	}
	return L.Push(true)
}

func cmdGetEnv(L lua.Lua) int {
	name, nameErr := L.ToString(-1)
	if nameErr != nil {
		return L.Push(nil)
	}
	value := os.Getenv(name)
	if len(value) > 0 {
		L.PushString(value)
	} else {
		L.PushNil()
	}
	return 1
}

func cmdExec(L lua.Lua) int {
	var err error
	if L.IsTable(1) {
		L.Len(1)
		n, _ := L.ToInteger(-1)
		L.Pop(1)
		args := make([]string, 0, n+1)
		for i := 0; i <= n; i++ {
			L.PushInteger(lua.Integer(i))
			L.GetTable(-2)
			arg1, err := L.ToString(-1)
			if err == nil && arg1 != "" {
				args = append(args, arg1)
			}
			L.Pop(1)
		}
		interpreter1 := interpreter.New()
		interpreter1.Args = args
		_, err = interpreter1.Spawnvp()
	} else {
		statement, statementErr := L.ToString(1)
		if statementErr != nil {
			return L.Push(nil, statementErr)
		}
		_, err = interpreter.New().Interpret(statement)
	}
	if err != nil {
		var out io.Writer = os.Stderr
		if cmd, cmdOk := LuaInstanceToCmd[L.State()]; cmdOk {
			out = cmd.Stderr
		}
		fmt.Fprintln(out, err)
		return L.Push(nil, err)
	}
	return L.Push(true)
}

type emptyWriter struct{}

func (e *emptyWriter) Write(b []byte) (int, error) {
	return len(b), nil
}

func cmdEval(L lua.Lua) int {
	statement, statementErr := L.ToString(1)
	if statementErr != nil {
		return L.Push(nil, statementErr)
	}
	r, w, err := os.Pipe()
	if err != nil {
		return L.Push(nil, err)
	}
	go func(statement string, w *os.File) {
		it := interpreter.New()
		it.SetStdout(w)
		it.Interpret(statement)
		w.Close()
	}(statement, w)

	var result = []byte{}
	for {
		buffer := make([]byte, 256)
		size, err := r.Read(buffer)
		if err != nil || size <= 0 {
			break
		}
		result = append(result, buffer[0:size]...)
	}
	r.Close()
	L.PushAnsiString(bytes.Trim(result, "\r\n\t "))
	return 1
}

func cmdWrite(L lua.Lua) int {
	var out io.Writer = os.Stdout
	cmd, cmdOk := LuaInstanceToCmd[L.State()]
	if cmdOk && cmd != nil && cmd.Stdout != nil {
		out = cmd.Stdout
	}
	return cmdWriteSub(L, out)
}

func cmdWriteErr(L lua.Lua) int {
	var out io.Writer = os.Stderr
	cmd, cmdOk := LuaInstanceToCmd[L.State()]
	if cmdOk && cmd != nil && cmd.Stderr != nil {
		out = cmd.Stderr
	}
	return cmdWriteSub(L, out)
}

func cmdWriteSub(L lua.Lua, out io.Writer) int {
	switch out.(type) {
	case *os.File:
		out = ansicolor.NewAnsiColorWriter(out)
	}
	n := L.GetTop()
	for i := 1; i <= n; i++ {
		str, err := L.ToString(i)
		if err != nil {
			return L.Push(nil, err)
		}
		if i > 1 {
			fmt.Fprint(out, "\t")
		}
		fmt.Fprint(out, str)
	}
	return L.Push(true)
}

func cmdGetwd(L lua.Lua) int {
	wd, err := dos.Getwd()
	if err == nil {
		return L.Push(wd)
	} else {
		return L.Push(nil, err)
	}
}

func cmdWhich(L lua.Lua) int {
	if L.GetType(-1) != lua.LUA_TSTRING {
		return 0
	}
	name, nameErr := L.ToString(-1)
	if nameErr != nil {
		return L.Push(nil, nameErr)
	}
	path, err := exec.LookPath(name)
	if err == nil {
		return L.Push(path)
	} else {
		return L.Push(nil, err)
	}
}

func cmdAtoU(L lua.Lua) int {
	str, err := dos.AtoU(L.ToAnsiString(1))
	if err == nil {
		L.PushString(str)
		return 1
	} else {
		return 0
	}
}

func cmdUtoA(L lua.Lua) int {
	utf8, utf8err := L.ToString(1)
	if utf8err != nil {
		return L.Push(nil, utf8err)
	}
	str, err := dos.UtoA(utf8)
	if err != nil {
		return L.Push(nil, err)
	}
	if len(str) >= 1 {
		L.PushAnsiString(str[:len(str)-1])
	} else {
		L.PushString("")
	}
	L.PushNil()
	return 2
}

func cmdGlob(L lua.Lua) int {
	result := make([]string, 0)
	for i := 1; ; i++ {
		wildcard, wildcardErr := L.ToString(i)
		if wildcard == "" || wildcardErr != nil {
			break
		}
		list, err := dos.Glob(wildcard)
		if list == nil || err != nil {
			result = append(result, wildcard)
		} else {
			result = append(result, list...)
		}
	}
	L.NewTable()
	for i := 0; i < len(result); i++ {
		L.PushString(result[i])
		L.RawSetI(-2, lua.Integer(i+1))
	}
	return 1
}

func cmdGetHistory(this lua.Lua) int {
	if this.GetType(-1) == lua.LUA_TNUMBER {
		val, err := this.ToInteger(-1)
		if err != nil {
			return this.Push(nil, err.Error())
		}
		this.PushString(conio.DefaultEditor.GetHistoryAt(val).Line)
	} else {
		this.PushInteger(lua.Integer(conio.DefaultEditor.HistoryLen()))
	}
	return 1
}

func cmdSetRuneWidth(this lua.Lua) int {
	char, charErr := this.ToInteger(1)
	if charErr != nil {
		return this.Push(nil, charErr)
	}
	width, widthErr := this.ToInteger(2)
	if widthErr != nil {
		return this.Push(nil, widthErr)
	}
	conio.SetCharWidth(rune(char), width)
	this.PushBool(true)
	return 1
}

func cmdShellExecute(this lua.Lua) int {
	action, actionErr := this.ToString(1)
	if actionErr != nil {
		return this.Push(nil, actionErr)
	}
	path, pathErr := this.ToString(2)
	if pathErr != nil {
		return this.Push(nil, pathErr)
	}
	param, paramErr := this.ToString(3)
	if paramErr != nil {
		param = ""
	}
	dir, dirErr := this.ToString(4)
	if dirErr != nil {
		dir = ""
	}
	err := dos.ShellExecute(action, path, param, dir)
	if err != nil {
		return this.Push(nil, err)
	} else {
		return this.Push(true)
	}
}

func cmdAccess(L lua.Lua) int {
	path, pathErr := L.ToString(1)
	if pathErr != nil {
		return L.Push(nil, pathErr)
	}
	mode, modeErr := L.ToInteger(2)
	if modeErr != nil {
		return L.Push(nil, modeErr)
	}
	fi, err := os.Stat(path)

	var result bool
	if err != nil || fi == nil {
		result = false
	} else {
		switch {
		case mode == 0:
			result = true
		case mode&1 != 0: // X_OK
		case mode&2 != 0: // W_OK
			result = fi.Mode().Perm()&0200 != 0
		case mode&4 != 0: // R_OK
			result = fi.Mode().Perm()&0400 != 0
		}
	}
	L.PushBool(result)
	return 1
}

func cmdPathJoin(L lua.Lua) int {
	path, pathErr := L.ToString(1)
	if pathErr != nil {
		return L.Push(nil, pathErr)
	}
	for i, i_ := 2, L.GetTop(); i <= i_; i++ {
		pathI, pathIErr := L.ToString(i)
		if pathIErr != nil {
			return L.Push(nil, pathErr)
		}
		path = dos.Join(path, pathI)
	}
	return L.Push(path, nil)
}

func cmdCommonPrefix(L lua.Lua) int {
	if L.GetType(1) != lua.LUA_TTABLE {
		return 0
	}
	list := []string{}
	for i := lua.Integer(1); true; i++ {
		L.PushInteger(i)
		L.GetTable(1)
		if str, err := L.ToString(2); err == nil && str != "" {
			list = append(list, str)
		} else {
			break
		}
		L.Remove(2)
	}
	L.PushString(completion.CommonPrefix(list))
	return 1
}

func cmdGetKey(L lua.Lua) int {
	keycode, scancode, shiftstatus := conio.GetKey()
	L.PushInteger(lua.Integer(keycode))
	L.PushInteger(lua.Integer(scancode))
	L.PushInteger(lua.Integer(shiftstatus))
	return 3
}

func cmdGetViewWidth(L lua.Lua) int {
	width, height := conio.GetScreenBufferInfo().ViewSize()
	L.PushInteger(lua.Integer(width))
	L.PushInteger(lua.Integer(height))
	return 2
}
<commit_msg>Fix: nyagos.alias.XXX always returns nil<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/shiena/ansicolor"

	"../alias"
	"../completion"
	"../conio"
	"../dos"
	"../interpreter"
	"../lua"
)

const alias_prefix = "nyagos.alias."

func cmdSetAlias(L lua.Lua) int {
	name, nameErr := L.ToString(-2)
	if nameErr != nil {
		return L.Push(nil, nameErr)
	}
	key := strings.ToLower(name)
	switch L.GetType(-1) {
	case lua.LUA_TSTRING:
		value, err := L.ToString(-1)
		regkey := alias_prefix + key
		L.SetField(lua.LUA_REGISTRYINDEX, regkey)
		if err == nil {
			alias.Table[key] = alias.New(value)
		} else {
			return L.Push(nil, err)
		}
	case lua.LUA_TFUNCTION:
		regkey := alias_prefix + key
		L.SetField(lua.LUA_REGISTRYINDEX, regkey)
		alias.Table[key] = LuaFunction{L, regkey}
	}
	return L.Push(true)
}

func cmdGetAlias(L lua.Lua) int {
	name, nameErr := L.ToString(-1)
	if nameErr != nil {
		return L.Push(nil, nameErr)
	}
	regkey := alias_prefix + strings.ToLower(name)
	L.GetField(lua.LUA_REGISTRYINDEX, regkey)
	return 1
}

func cmdSetEnv(L lua.Lua) int {
	name, nameErr := L.ToString(-2)
	if nameErr != nil {
		return L.Push(nil, nameErr)
	}
	value, valueErr := L.ToString(-1)
	if valueErr != nil {
		return L.Push(nil, valueErr)
	}
	if len(value) > 0 {
		os.Setenv(name, value)
	} else {
		os.Unsetenv(name)
	}
	return L.Push(true)
}

func cmdGetEnv(L lua.Lua) int {
	name, nameErr := L.ToString(-1)
	if nameErr != nil {
		return L.Push(nil)
	}
	value := os.Getenv(name)
	if len(value) > 0 {
		L.PushString(value)
	} else {
		L.PushNil()
	}
	return 1
}

func cmdExec(L lua.Lua) int {
	var err error
	if L.IsTable(1) {
		L.Len(1)
		n, _ := L.ToInteger(-1)
		L.Pop(1)
		args := make([]string, 0, n+1)
		for i := 0; i <= n; i++ {
			L.PushInteger(lua.Integer(i))
			L.GetTable(-2)
			arg1, err := L.ToString(-1)
			if err == nil && arg1 != "" {
				args = append(args, arg1)
			}
			L.Pop(1)
		}
		interpreter1 := interpreter.New()
		interpreter1.Args = args
		_, err = interpreter1.Spawnvp()
	} else {
		statement, statementErr := L.ToString(1)
		if statementErr != nil {
			return L.Push(nil, statementErr)
		}
		_, err = interpreter.New().Interpret(statement)
	}
	if err != nil {
		var out io.Writer = os.Stderr
		if cmd, cmdOk := LuaInstanceToCmd[L.State()]; cmdOk {
			out = cmd.Stderr
		}
		fmt.Fprintln(out, err)
		return L.Push(nil, err)
	}
	return L.Push(true)
}

type emptyWriter struct{}

func (e *emptyWriter) Write(b []byte) (int, error) {
	return len(b), nil
}

func cmdEval(L lua.Lua) int {
	statement, statementErr := L.ToString(1)
	if statementErr != nil {
		return L.Push(nil, statementErr)
	}
	r, w, err := os.Pipe()
	if err != nil {
		return L.Push(nil, err)
	}
	go func(statement string, w *os.File) {
		it := interpreter.New()
		it.SetStdout(w)
		it.Interpret(statement)
		w.Close()
	}(statement, w)

	var result = []byte{}
	for {
		buffer := make([]byte, 256)
		size, err := r.Read(buffer)
		if err != nil || size <= 0 {
			break
		}
		result = append(result, buffer[0:size]...)
	}
	r.Close()
	L.PushAnsiString(bytes.Trim(result, "\r\n\t "))
	return 1
}

func cmdWrite(L lua.Lua) int {
	var out io.Writer = os.Stdout
	cmd, cmdOk := LuaInstanceToCmd[L.State()]
	if cmdOk && cmd != nil && cmd.Stdout != nil {
		out = cmd.Stdout
	}
	return cmdWriteSub(L, out)
}

func cmdWriteErr(L lua.Lua) int {
	var out io.Writer = os.Stderr
	cmd, cmdOk := LuaInstanceToCmd[L.State()]
	if cmdOk && cmd != nil && cmd.Stderr != nil {
		out = cmd.Stderr
	}
	return cmdWriteSub(L, out)
}

func cmdWriteSub(L lua.Lua, out io.Writer) int {
	switch out.(type) {
	case *os.File:
		out = ansicolor.NewAnsiColorWriter(out)
	}
	n := L.GetTop()
	for i := 1; i <= n; i++ {
		str, err := L.ToString(i)
		if err != nil {
			return L.Push(nil, err)
		}
		if i > 1 {
			fmt.Fprint(out, "\t")
		}
		fmt.Fprint(out, str)
	}
	return L.Push(true)
}

func cmdGetwd(L lua.Lua) int {
	wd, err := dos.Getwd()
	if err == nil {
		return L.Push(wd)
	} else {
		return L.Push(nil, err)
	}
}

func cmdWhich(L lua.Lua) int {
	if L.GetType(-1) != lua.LUA_TSTRING {
		return 0
	}
	name, nameErr := L.ToString(-1)
	if nameErr != nil {
		return L.Push(nil, nameErr)
	}
	path, err := exec.LookPath(name)
	if err == nil {
		return L.Push(path)
	} else {
		return L.Push(nil, err)
	}
}

func cmdAtoU(L lua.Lua) int {
	str, err := dos.AtoU(L.ToAnsiString(1))
	if err == nil {
		L.PushString(str)
		return 1
	} else {
		return 0
	}
}

func cmdUtoA(L lua.Lua) int {
	utf8, utf8err := L.ToString(1)
	if utf8err != nil {
		return L.Push(nil, utf8err)
	}
	str, err := dos.UtoA(utf8)
	if err != nil {
		return L.Push(nil, err)
	}
	if len(str) >= 1 {
		L.PushAnsiString(str[:len(str)-1])
	} else {
		L.PushString("")
	}
	L.PushNil()
	return 2
}

func cmdGlob(L lua.Lua) int {
	result := make([]string, 0)
	for i := 1; ; i++ {
		wildcard, wildcardErr := L.ToString(i)
		if wildcard == "" || wildcardErr != nil {
			break
		}
		list, err := dos.Glob(wildcard)
		if list == nil || err != nil {
			result = append(result, wildcard)
		} else {
			result = append(result, list...)
		}
	}
	L.NewTable()
	for i := 0; i < len(result); i++ {
		L.PushString(result[i])
		L.RawSetI(-2, lua.Integer(i+1))
	}
	return 1
}

func cmdGetHistory(this lua.Lua) int {
	if this.GetType(-1) == lua.LUA_TNUMBER {
		val, err := this.ToInteger(-1)
		if err != nil {
			return this.Push(nil, err.Error())
		}
		this.PushString(conio.DefaultEditor.GetHistoryAt(val).Line)
	} else {
		this.PushInteger(lua.Integer(conio.DefaultEditor.HistoryLen()))
	}
	return 1
}

func cmdSetRuneWidth(this lua.Lua) int {
	char, charErr := this.ToInteger(1)
	if charErr != nil {
		return this.Push(nil, charErr)
	}
	width, widthErr := this.ToInteger(2)
	if widthErr != nil {
		return this.Push(nil, widthErr)
	}
	conio.SetCharWidth(rune(char), width)
	this.PushBool(true)
	return 1
}

func cmdShellExecute(this lua.Lua) int {
	action, actionErr := this.ToString(1)
	if actionErr != nil {
		return this.Push(nil, actionErr)
	}
	path, pathErr := this.ToString(2)
	if pathErr != nil {
		return this.Push(nil, pathErr)
	}
	param, paramErr := this.ToString(3)
	if paramErr != nil {
		param = ""
	}
	dir, dirErr := this.ToString(4)
	if dirErr != nil {
		dir = ""
	}
	err := dos.ShellExecute(action, path, param, dir)
	if err != nil {
		return this.Push(nil, err)
	} else {
		return this.Push(true)
	}
}

func cmdAccess(L lua.Lua) int {
	path, pathErr := L.ToString(1)
	if pathErr != nil {
		return L.Push(nil, pathErr)
	}
	mode, modeErr := L.ToInteger(2)
	if modeErr != nil {
		return L.Push(nil, modeErr)
	}
	fi, err := os.Stat(path)

	var result bool
	if err != nil || fi == nil {
		result = false
	} else {
		switch {
		case mode == 0:
			result = true
		case mode&1 != 0: // X_OK
		case mode&2 != 0: // W_OK
			result = fi.Mode().Perm()&0200 != 0
		case mode&4 != 0: // R_OK
			result = fi.Mode().Perm()&0400 != 0
		}
	}
	L.PushBool(result)
	return 1
}

func cmdPathJoin(L lua.Lua) int {
	path, pathErr := L.ToString(1)
	if pathErr != nil {
		return L.Push(nil, pathErr)
	}
	for i, i_ := 2, L.GetTop(); i <= i_; i++ {
		pathI, pathIErr := L.ToString(i)
		if pathIErr != nil {
			return L.Push(nil, pathErr)
		}
		path = dos.Join(path, pathI)
	}
	return L.Push(path, nil)
}

func cmdCommonPrefix(L lua.Lua) int {
	if L.GetType(1) != lua.LUA_TTABLE {
		return 0
	}
	list := []string{}
	for i := lua.Integer(1); true; i++ {
		L.PushInteger(i)
		L.GetTable(1)
		if str, err := L.ToString(2); err == nil && str != "" {
			list = append(list, str)
		} else {
			break
		}
		L.Remove(2)
	}
	L.PushString(completion.CommonPrefix(list))
	return 1
}

func cmdGetKey(L lua.Lua) int {
	keycode, scancode, shiftstatus := conio.GetKey()
	L.PushInteger(lua.Integer(keycode))
	L.PushInteger(lua.Integer(scancode))
	L.PushInteger(lua.Integer(shiftstatus))
	return 3
}

func cmdGetViewWidth(L lua.Lua) int {
	width, height := conio.GetScreenBufferInfo().ViewSize()
	L.PushInteger(lua.Integer(width))
	L.PushInteger(lua.Integer(height))
	return 2
}
<|endoftext|>


<commit_before>// Copyright 2017 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 maintner

import (
	"bufio"
	"bytes"
	"context"
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"golang.org/x/build/maintner/maintpb"
)

type gitHash interface {
	String() string
	Less(gitHash) bool
}

// gitSHA1 (the value type) is the current (only) implementation of
// the gitHash interface.
type gitSHA1 [20]byte

func (h gitSHA1) String() string { return fmt.Sprintf("%x", h[:]) }
func (h gitSHA1) Less(h2 gitHash) bool {
	switch h2 := h2.(type) {
	case gitSHA1:
		return bytes.Compare(h[:], h2[:]) < 0
	default:
		panic("unsupported type")
	}
}

func gitHashFromHexStr(s string) gitHash {
	if len(s) != 40 {
		panic(fmt.Sprintf("bogus git hash %q", s))
	}
	var hash gitSHA1
	n, err := hex.Decode(hash[:], []byte(s)) // TODO: garbage
	if n != 20 || err != nil {
		panic(fmt.Sprintf("bogus git hash %q", s))
	}
	return hash
}

func gitHashFromHex(s []byte) gitHash {
	if len(s) != 40 {
		panic(fmt.Sprintf("bogus git hash %q", s))
	}
	var hash gitSHA1
	n, err := hex.Decode(hash[:], s)
	if n != 20 || err != nil {
		panic(fmt.Sprintf("bogus git hash %q", s))
	}
	return hash
}

type gitCommit struct {
	hash       gitHash
	tree       gitHash
	parents    []gitHash
	author     *gitPerson
	authorTime time.Time
	committer  *gitPerson
	commitTime time.Time
	msg        string
	files      []*maintpb.GitDiffTreeFile
}

type gitPerson struct {
	str string // "Foo Bar <foo@bar.com>"
}

// requires c.mu be held for writing.
func (c *Corpus) enqueueCommitLocked(h gitHash) {
	if _, ok := c.gitCommit[h]; ok {
		return
	}
	if c.gitCommitTodo == nil {
		c.gitCommitTodo = map[gitHash]bool{}
	}
	c.gitCommitTodo[h] = true
}

// PollGithubCommits polls for git commits in a directory.
func (c *Corpus) PollGitCommits(ctx context.Context, conf polledGitCommits) error {
	cmd := exec.Command("git", "show-ref", "refs/remotes/origin/master")
	cmd.Dir = os.Getenv("GOROOT")
	out, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	outs := strings.TrimSpace(string(out))
	if outs == "" {
		return fmt.Errorf("no remote found for refs/remotes/origin/master")
	}
	ref := strings.Fields(outs)[0]
	refHash := gitHashFromHexStr(ref)
	c.mu.Lock()
	c.enqueueCommitLocked(refHash)
	c.mu.Unlock()

	idle := false
	for {
		hash := c.gitCommitToIndex()
		if hash == nil {
			if !idle {
				log.Printf("All git commits index for %v; idle.", conf.repo)
				idle = true
			}
			time.Sleep(5 * time.Second)
			continue
		}
		if err := c.indexCommit(conf, hash); err != nil {
			log.Printf("Error indexing %v: %v", hash, err)
			// TODO: temporary vs permanent failure? reschedule? fail hard?
			// For now just loop with a sleep.
			time.Sleep(5 * time.Second)
		}
	}
	log.Printf("TODO: poll %v from %v", conf.repo, conf.dir)
	select {} // TODO(bradfitz): actuall poll
	return nil
}

// returns nil if no work.
func (c *Corpus) gitCommitToIndex() gitHash {
	c.mu.RLock()
	defer c.mu.RUnlock()
	for hash := range c.gitCommitTodo {
		return hash
	}
	return nil
}

var (
	nlnl           = []byte("\n\n")
	parentSpace    = []byte("parent ")
	authorSpace    = []byte("author ")
	committerSpace = []byte("committer ")
	treeSpace      = []byte("tree ")
	golangHgSpace  = []byte("golang-hg ")
)

func (c *Corpus) indexCommit(conf polledGitCommits, hash gitHash) error {
	if conf.repo == nil {
		panic("bogus config; nil repo")
	}
	cmd := exec.Command("git", "cat-file", "commit", hash.String())
	cmd.Dir = conf.dir
	catFile, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("git cat-file -p %v: %v", hash, err)
	}
	cmd = exec.Command("git", "diff-tree", "--numstat", hash.String())
	cmd.Dir = conf.dir
	diffTreeOut, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("git diff-tree --numstat %v: %v", hash, err)
	}

	c.mu.Lock()
	if _, ok := c.gitCommit[hash]; ok {
		c.mu.Unlock()
		return nil
	}
	c.mu.Unlock()
	diffTree := &maintpb.GitDiffTree{}
	bs := bufio.NewScanner(bytes.NewReader(diffTreeOut))
	lineNum := 0
	for bs.Scan() {
		line := strings.TrimSpace(bs.Text())
		lineNum++
		if lineNum == 1 && line == hash.String() {
			continue
		}
		f := strings.Fields(line)
		// A line is like: <added> WS+ <deleted> WS+ <filename>
		// Where <added> or <deleted> can be '-' to mean binary.
		// The filename could contain spaces.
		// 49      8       maintner/maintner.go
		// Or:
		// 49      8       some/name with spaces.txt
		if len(f) < 3 {
			continue
		}
		binary := f[0] == "-" || f[1] == "-"
		added, _ := strconv.ParseInt(f[0], 10, 64)
		deleted, _ := strconv.ParseInt(f[1], 10, 64)
		file := strings.TrimPrefix(line, f[0])
		file = strings.TrimSpace(file)
		file = strings.TrimPrefix(file, f[1])
		file = strings.TrimSpace(file)

		diffTree.File = append(diffTree.File, &maintpb.GitDiffTreeFile{
			File:    file,
			Added:   added,
			Deleted: deleted,
			Binary:  binary,
		})
	}
	if err := bs.Err(); err != nil {
		return err
	}
	commit := &maintpb.GitCommit{
		Raw:      catFile,
		DiffTree: diffTree,
	}
	switch hash.(type) {
	case gitSHA1:
		commit.Sha1 = hash.String()
	default:
		return fmt.Errorf("unsupported git hash type %T", hash)
	}
	m := &maintpb.Mutation{
		Git: &maintpb.GitMutation{
			Repo:   conf.repo,
			Commit: commit,
		},
	}
	c.processMutation(m)
	return nil
}

// Note: c.mu is held for writing.
func (c *Corpus) processGitMutation(m *maintpb.GitMutation) {
	commit := m.Commit
	if commit == nil {
		return
	}
	if len(commit.Sha1) != 40 {
		return
	}
	hash := gitHashFromHexStr(commit.Sha1)

	catFile := commit.Raw
	i := bytes.Index(catFile, nlnl)
	if i == 0 {
		log.Printf("Unparseable commit %q", hash)
		return
	}
	hdr, msg := catFile[:i], catFile[i+2:]
	gc := &gitCommit{
		hash:    hash,
		parents: make([]gitHash, 0, bytes.Count(hdr, parentSpace)),
		msg:     string(msg),
	}
	if commit.DiffTree != nil {
		gc.files = commit.DiffTree.File
	}
	for _, f := range gc.files {
		f.File = c.str(f.File) // intern the string
	}
	parents := 0
	err := foreachLine(hdr, func(ln []byte) error {
		if bytes.HasPrefix(ln, parentSpace) {
			parents++
			parentHash := gitHashFromHex(ln[len(parentSpace):])
			gc.parents = append(gc.parents, parentHash)
			c.enqueueCommitLocked(parentHash)
			return nil
		}
		if bytes.HasPrefix(ln, authorSpace) {
			p, t, err := c.parsePerson(ln[len(authorSpace):])
			if err != nil {
				return fmt.Errorf("unrecognized author line %q: %v", ln, err)
			}
			gc.author = p
			gc.authorTime = t
			return nil
		}
		if bytes.HasPrefix(ln, committerSpace) {
			p, t, err := c.parsePerson(ln[len(committerSpace):])
			if err != nil {
				return fmt.Errorf("unrecognized committer line %q: %v", ln, err)
			}
			gc.committer = p
			gc.commitTime = t
			return nil
		}
		if bytes.HasPrefix(ln, treeSpace) {
			gc.tree = gitHashFromHex(ln[len(treeSpace):])
			return nil
		}
		if bytes.HasPrefix(ln, golangHgSpace) {
			if c.gitOfHg == nil {
				c.gitOfHg = map[string]gitHash{}
			}
			c.gitOfHg[string(ln[len(golangHgSpace):])] = hash
			return nil
		}
		log.Printf("in commit %s, unrecognized line %q", hash, ln)
		return nil
	})
	if err != nil {
		log.Printf("Unparseable commit %q: %v", hash, err)
		return
	}
	if c.gitCommit == nil {
		c.gitCommit = map[gitHash]*gitCommit{}
	}
	c.gitCommit[hash] = gc
	if c.gitCommitTodo != nil {
		delete(c.gitCommitTodo, hash)
	}
	if n := len(c.gitCommit); n%100 == 0 {
		log.Printf("Num git commits = %v", n)
	}
}

// calls f on each non-empty line in v, without the trailing \n. the
// final line need not include a trailing \n. Returns first non-nil
// error returned by f.
func foreachLine(v []byte, f func([]byte) error) error {
	for len(v) > 0 {
		i := bytes.IndexByte(v, '\n')
		if i < 0 {
			return f(v)
		}
		if err := f(v[:i]); err != nil {
			return err
		}
		v = v[i+1:]
	}
	return nil
}

var personRx = regexp.MustCompile(`^(.+) (\d+) ([\+\-]\d\d\d\d)\s*$`)

//
// parsePerson parses an "author" or "committer" value from "git cat-file -p COMMIT"
// The values are like:
//    Foo Bar <foobar@gmail.com> 1488624439 +0900
// c.mu must be held for writing.
func (c *Corpus) parsePerson(v []byte) (*gitPerson, time.Time, error) {
	m := personRx.FindSubmatch(v) // TODO(bradfitz): for speed, don't use regexp :(
	if m == nil {
		return nil, time.Time{}, errors.New("failed to match person")
	}

	ut, err := strconv.ParseInt(string(m[2]), 10, 64)
	if err != nil {
		return nil, time.Time{}, err
	}
	t := time.Unix(ut, 0).In(c.gitLocation(string(m[3])))

	p, ok := c.gitPeople[string(m[1])]
	if !ok {
		p = &gitPerson{str: string(m[1])}
		if c.gitPeople == nil {
			c.gitPeople = map[string]*gitPerson{}
		}
		c.gitPeople[p.str] = p
	}
	return p, t, nil

}

// v is like '[+-]hhmm'
// c.mu must be held for writing.
func (c *Corpus) gitLocation(v string) *time.Location {
	if loc, ok := c.zoneCache[v]; ok {
		return loc
	}
	h, _ := strconv.Atoi(v[1:3])
	m, _ := strconv.Atoi(v[3:5])
	east := 1
	if v[0] == '-' {
		east = -1
	}
	loc := time.FixedZone(v, east*(h*3600+m*60))
	if c.zoneCache == nil {
		c.zoneCache = map[string]*time.Location{}
	}
	c.zoneCache[v] = loc
	return loc
}

type FileCount struct {
	File  string
	Count int
}

// queryFrequentlyModifiedFiles is an example query just for fun.
// It is not currently used by anything.
func (c *Corpus) QueryFrequentlyModifiedFiles(topN int) []FileCount {
	c.mu.RLock()
	defer c.mu.RUnlock()
	n := map[string]int{} // file -> count
	for _, gc := range c.gitCommit {
		for _, f := range gc.files {
			n[modernizeFilename(f.File)]++
		}
	}
	files := make([]FileCount, 0, len(n))
	for file, count := range n {
		files = append(files, FileCount{file, count})
	}
	sort.Slice(files, func(i, j int) bool {
		return files[i].Count > files[j].Count
	})
	if len(files) > topN {
		files = files[:topN]
	}
	return files
}

func modernizeFilename(f string) string {
	if strings.HasPrefix(f, "src/pkg/") {
		f = "src/" + strings.TrimPrefix(f, "src/pkg/")
	}
	if strings.HasPrefix(f, "src/http/") {
		f = "src/net/http/" + strings.TrimPrefix(f, "src/http/")
	}
	return f
}
<commit_msg>maintner: pass through context<commit_after>// Copyright 2017 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 maintner

import (
	"bufio"
	"bytes"
	"context"
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"golang.org/x/build/maintner/maintpb"
)

type gitHash interface {
	String() string
	Less(gitHash) bool
}

// gitSHA1 (the value type) is the current (only) implementation of
// the gitHash interface.
type gitSHA1 [20]byte

func (h gitSHA1) String() string { return fmt.Sprintf("%x", h[:]) }
func (h gitSHA1) Less(h2 gitHash) bool {
	switch h2 := h2.(type) {
	case gitSHA1:
		return bytes.Compare(h[:], h2[:]) < 0
	default:
		panic("unsupported type")
	}
}

func gitHashFromHexStr(s string) gitHash {
	if len(s) != 40 {
		panic(fmt.Sprintf("bogus git hash %q", s))
	}
	var hash gitSHA1
	n, err := hex.Decode(hash[:], []byte(s)) // TODO: garbage
	if n != 20 || err != nil {
		panic(fmt.Sprintf("bogus git hash %q", s))
	}
	return hash
}

func gitHashFromHex(s []byte) gitHash {
	if len(s) != 40 {
		panic(fmt.Sprintf("bogus git hash %q", s))
	}
	var hash gitSHA1
	n, err := hex.Decode(hash[:], s)
	if n != 20 || err != nil {
		panic(fmt.Sprintf("bogus git hash %q", s))
	}
	return hash
}

type gitCommit struct {
	hash       gitHash
	tree       gitHash
	parents    []gitHash
	author     *gitPerson
	authorTime time.Time
	committer  *gitPerson
	commitTime time.Time
	msg        string
	files      []*maintpb.GitDiffTreeFile
}

type gitPerson struct {
	str string // "Foo Bar <foo@bar.com>"
}

// requires c.mu be held for writing.
func (c *Corpus) enqueueCommitLocked(h gitHash) {
	if _, ok := c.gitCommit[h]; ok {
		return
	}
	if c.gitCommitTodo == nil {
		c.gitCommitTodo = map[gitHash]bool{}
	}
	c.gitCommitTodo[h] = true
}

// PollGithubCommits polls for git commits in a directory.
func (c *Corpus) PollGitCommits(ctx context.Context, conf polledGitCommits) error {
	cmd := exec.CommandContext(ctx, "git", "show-ref", "refs/remotes/origin/master")
	cmd.Dir = os.Getenv("GOROOT")
	out, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	outs := strings.TrimSpace(string(out))
	if outs == "" {
		return fmt.Errorf("no remote found for refs/remotes/origin/master")
	}
	ref := strings.Fields(outs)[0]
	refHash := gitHashFromHexStr(ref)
	c.mu.Lock()
	c.enqueueCommitLocked(refHash)
	c.mu.Unlock()

	idle := false
	for {
		hash := c.gitCommitToIndex()
		if hash == nil {
			if !idle {
				log.Printf("All git commits index for %v; idle.", conf.repo)
				idle = true
			}
			time.Sleep(5 * time.Second)
			continue
		}
		if err := c.indexCommit(conf, hash); err != nil {
			log.Printf("Error indexing %v: %v", hash, err)
			select {
			case <-ctx.Done():
				return ctx.Err()
			// TODO: temporary vs permanent failure? reschedule? fail hard?
			// For now just loop with a sleep.
			case <-time.After(5 * time.Second):
			}
		}
	}
	log.Printf("TODO: poll %v from %v", conf.repo, conf.dir)
	select {} // TODO(bradfitz): actuall poll
	return nil
}

// returns nil if no work.
func (c *Corpus) gitCommitToIndex() gitHash {
	c.mu.RLock()
	defer c.mu.RUnlock()
	for hash := range c.gitCommitTodo {
		return hash
	}
	return nil
}

var (
	nlnl           = []byte("\n\n")
	parentSpace    = []byte("parent ")
	authorSpace    = []byte("author ")
	committerSpace = []byte("committer ")
	treeSpace      = []byte("tree ")
	golangHgSpace  = []byte("golang-hg ")
)

func (c *Corpus) indexCommit(conf polledGitCommits, hash gitHash) error {
	if conf.repo == nil {
		panic("bogus config; nil repo")
	}
	cmd := exec.Command("git", "cat-file", "commit", hash.String())
	cmd.Dir = conf.dir
	catFile, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("git cat-file -p %v: %v", hash, err)
	}
	cmd = exec.Command("git", "diff-tree", "--numstat", hash.String())
	cmd.Dir = conf.dir
	diffTreeOut, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("git diff-tree --numstat %v: %v", hash, err)
	}

	c.mu.Lock()
	if _, ok := c.gitCommit[hash]; ok {
		c.mu.Unlock()
		return nil
	}
	c.mu.Unlock()
	diffTree := &maintpb.GitDiffTree{}
	bs := bufio.NewScanner(bytes.NewReader(diffTreeOut))
	lineNum := 0
	for bs.Scan() {
		line := strings.TrimSpace(bs.Text())
		lineNum++
		if lineNum == 1 && line == hash.String() {
			continue
		}
		f := strings.Fields(line)
		// A line is like: <added> WS+ <deleted> WS+ <filename>
		// Where <added> or <deleted> can be '-' to mean binary.
		// The filename could contain spaces.
		// 49      8       maintner/maintner.go
		// Or:
		// 49      8       some/name with spaces.txt
		if len(f) < 3 {
			continue
		}
		binary := f[0] == "-" || f[1] == "-"
		added, _ := strconv.ParseInt(f[0], 10, 64)
		deleted, _ := strconv.ParseInt(f[1], 10, 64)
		file := strings.TrimPrefix(line, f[0])
		file = strings.TrimSpace(file)
		file = strings.TrimPrefix(file, f[1])
		file = strings.TrimSpace(file)

		diffTree.File = append(diffTree.File, &maintpb.GitDiffTreeFile{
			File:    file,
			Added:   added,
			Deleted: deleted,
			Binary:  binary,
		})
	}
	if err := bs.Err(); err != nil {
		return err
	}
	commit := &maintpb.GitCommit{
		Raw:      catFile,
		DiffTree: diffTree,
	}
	switch hash.(type) {
	case gitSHA1:
		commit.Sha1 = hash.String()
	default:
		return fmt.Errorf("unsupported git hash type %T", hash)
	}
	m := &maintpb.Mutation{
		Git: &maintpb.GitMutation{
			Repo:   conf.repo,
			Commit: commit,
		},
	}
	c.processMutation(m)
	return nil
}

// Note: c.mu is held for writing.
func (c *Corpus) processGitMutation(m *maintpb.GitMutation) {
	commit := m.Commit
	if commit == nil {
		return
	}
	if len(commit.Sha1) != 40 {
		return
	}
	hash := gitHashFromHexStr(commit.Sha1)

	catFile := commit.Raw
	i := bytes.Index(catFile, nlnl)
	if i == 0 {
		log.Printf("Unparseable commit %q", hash)
		return
	}
	hdr, msg := catFile[:i], catFile[i+2:]
	gc := &gitCommit{
		hash:    hash,
		parents: make([]gitHash, 0, bytes.Count(hdr, parentSpace)),
		msg:     string(msg),
	}
	if commit.DiffTree != nil {
		gc.files = commit.DiffTree.File
	}
	for _, f := range gc.files {
		f.File = c.str(f.File) // intern the string
	}
	parents := 0
	err := foreachLine(hdr, func(ln []byte) error {
		if bytes.HasPrefix(ln, parentSpace) {
			parents++
			parentHash := gitHashFromHex(ln[len(parentSpace):])
			gc.parents = append(gc.parents, parentHash)
			c.enqueueCommitLocked(parentHash)
			return nil
		}
		if bytes.HasPrefix(ln, authorSpace) {
			p, t, err := c.parsePerson(ln[len(authorSpace):])
			if err != nil {
				return fmt.Errorf("unrecognized author line %q: %v", ln, err)
			}
			gc.author = p
			gc.authorTime = t
			return nil
		}
		if bytes.HasPrefix(ln, committerSpace) {
			p, t, err := c.parsePerson(ln[len(committerSpace):])
			if err != nil {
				return fmt.Errorf("unrecognized committer line %q: %v", ln, err)
			}
			gc.committer = p
			gc.commitTime = t
			return nil
		}
		if bytes.HasPrefix(ln, treeSpace) {
			gc.tree = gitHashFromHex(ln[len(treeSpace):])
			return nil
		}
		if bytes.HasPrefix(ln, golangHgSpace) {
			if c.gitOfHg == nil {
				c.gitOfHg = map[string]gitHash{}
			}
			c.gitOfHg[string(ln[len(golangHgSpace):])] = hash
			return nil
		}
		log.Printf("in commit %s, unrecognized line %q", hash, ln)
		return nil
	})
	if err != nil {
		log.Printf("Unparseable commit %q: %v", hash, err)
		return
	}
	if c.gitCommit == nil {
		c.gitCommit = map[gitHash]*gitCommit{}
	}
	c.gitCommit[hash] = gc
	if c.gitCommitTodo != nil {
		delete(c.gitCommitTodo, hash)
	}
	if n := len(c.gitCommit); n%100 == 0 {
		log.Printf("Num git commits = %v", n)
	}
}

// calls f on each non-empty line in v, without the trailing \n. the
// final line need not include a trailing \n. Returns first non-nil
// error returned by f.
func foreachLine(v []byte, f func([]byte) error) error {
	for len(v) > 0 {
		i := bytes.IndexByte(v, '\n')
		if i < 0 {
			return f(v)
		}
		if err := f(v[:i]); err != nil {
			return err
		}
		v = v[i+1:]
	}
	return nil
}

var personRx = regexp.MustCompile(`^(.+) (\d+) ([\+\-]\d\d\d\d)\s*$`)

//
// parsePerson parses an "author" or "committer" value from "git cat-file -p COMMIT"
// The values are like:
//    Foo Bar <foobar@gmail.com> 1488624439 +0900
// c.mu must be held for writing.
func (c *Corpus) parsePerson(v []byte) (*gitPerson, time.Time, error) {
	m := personRx.FindSubmatch(v) // TODO(bradfitz): for speed, don't use regexp :(
	if m == nil {
		return nil, time.Time{}, errors.New("failed to match person")
	}

	ut, err := strconv.ParseInt(string(m[2]), 10, 64)
	if err != nil {
		return nil, time.Time{}, err
	}
	t := time.Unix(ut, 0).In(c.gitLocation(string(m[3])))

	p, ok := c.gitPeople[string(m[1])]
	if !ok {
		p = &gitPerson{str: string(m[1])}
		if c.gitPeople == nil {
			c.gitPeople = map[string]*gitPerson{}
		}
		c.gitPeople[p.str] = p
	}
	return p, t, nil

}

// v is like '[+-]hhmm'
// c.mu must be held for writing.
func (c *Corpus) gitLocation(v string) *time.Location {
	if loc, ok := c.zoneCache[v]; ok {
		return loc
	}
	h, _ := strconv.Atoi(v[1:3])
	m, _ := strconv.Atoi(v[3:5])
	east := 1
	if v[0] == '-' {
		east = -1
	}
	loc := time.FixedZone(v, east*(h*3600+m*60))
	if c.zoneCache == nil {
		c.zoneCache = map[string]*time.Location{}
	}
	c.zoneCache[v] = loc
	return loc
}

type FileCount struct {
	File  string
	Count int
}

// queryFrequentlyModifiedFiles is an example query just for fun.
// It is not currently used by anything.
func (c *Corpus) QueryFrequentlyModifiedFiles(topN int) []FileCount {
	c.mu.RLock()
	defer c.mu.RUnlock()
	n := map[string]int{} // file -> count
	for _, gc := range c.gitCommit {
		for _, f := range gc.files {
			n[modernizeFilename(f.File)]++
		}
	}
	files := make([]FileCount, 0, len(n))
	for file, count := range n {
		files = append(files, FileCount{file, count})
	}
	sort.Slice(files, func(i, j int) bool {
		return files[i].Count > files[j].Count
	})
	if len(files) > topN {
		files = files[:topN]
	}
	return files
}

func modernizeFilename(f string) string {
	if strings.HasPrefix(f, "src/pkg/") {
		f = "src/" + strings.TrimPrefix(f, "src/pkg/")
	}
	if strings.HasPrefix(f, "src/http/") {
		f = "src/net/http/" + strings.TrimPrefix(f, "src/http/")
	}
	return f
}
<|endoftext|>