<commit_before>package golden

import (
	"bytes"
	"errors"
	"github.com/abcum/lcp"
	"github.com/jkomoros/boardgame"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"text/template"
)

var goldenTestTemplate *template.Template

func init() {
	goldenTestTemplate = template.Must(template.New("golden").Parse(goldenTestTemplateText))
}

//LinkGoldenFolders helps create a folder system for the `filesystem` storage
//layer, with the individual folders pointing back to folders adjacent to the
//games they're affiliated with. This is useful when you want to generate new
//golden tests for game types. First we use reflection to find the package
//path for each delegate, ensure a folder exists within it with tbe
//goldenFolderName name, create a soft-link from basePath to that folder, and
//create a `golden_test.go` file that automatically tests all of those golden
//files (and assumes that your package defines a `NewDelegate()
//boardgame.GameDelegate` method). The result is that the underlying files
//will be stored in folders adjacent to the games they are relative to, which
//is convenient if you're adding new golden games to the test set. The
//`filesystem` storage layer will call this as a convenience if you pass a
//non-"" goldenFolderName to filesystem.NewStorageManager.
func LinkGoldenFolders(basePath string, goldenFolderName string, managers []*boardgame.GameManager) error {

	if goldenFolderName == "" {
		goldenFolderName = "golden"
	}

	if _, err := os.Stat(basePath); os.IsNotExist(err) {
		return errors.New(basePath + " does not already exist.")
	}

	for _, manager := range managers {
		pkgPath := reflect.ValueOf(manager.Delegate()).Elem().Type().PkgPath()
		if err := linkGoldenFolder(manager.Delegate().Name(), pkgPath, basePath, goldenFolderName); err != nil {
			return errors.New("Couldn't link golden folder for " + manager.Delegate().Name() + ": " + err.Error())
		}
	}

	return nil
}

func linkGoldenFolder(gameType, pkgPath, basePath, goldenFolderName string) error {

	//TODO: should this be public?

	//TODO: does this handle vendoring correctly?

	goPath := os.Getenv("GOPATH")

	if goPath == "" {
		return errors.New("Gopath wasn't set")
	}

	fullPkgPath := filepath.Join(goPath, "src", pkgPath)

	fullPath := filepath.Join(fullPkgPath, goldenFolderName)

	if _, err := os.Stat(fullPath); os.IsNotExist(err) {
		log.Println("Creating " + fullPath)
		if err := os.Mkdir(fullPath, 0700); err != nil {
			return errors.New("Could not make golden path: " + err.Error())
		}
	}

	gamePath := filepath.Join(basePath, gameType)

	if _, err := os.Stat(gamePath); os.IsNotExist(err) {

		execPath, err := os.Executable()

		if err != nil {
			return errors.New("Couldn't get executable path: " + err.Error())
		}

		relPath, err := relativizePaths(execPath, fullPath)

		if err != nil {
			return errors.New("Couldn't relativize paths: " + err.Error())
		}

		log.Println("Linking " + gamePath + " to " + relPath)

		//Soft link from basePath.

		if err := os.Symlink(relPath, gamePath); err != nil {
			return errors.New("Couldn't create symlink: " + err.Error())
		}
	}

	//TODO: allow this to be skipped as an otpion.
	if err := createGoldenTest(fullPkgPath, goldenFolderName); err != nil {
		return errors.New("Couldn't store golden test: " + err.Error())
	}

	return nil

}

//relativizePaths takes two absolute paths and returns a string that is the
//relative path from from to to.
func relativizePaths(from, to string) (string, error) {

	if !filepath.IsAbs(from) {
		return "", errors.New("From is not absolute")
	}

	if !filepath.IsAbs(to) {
		return "", errors.New("To is not absolute")
	}

	from = filepath.Clean(from)
	to = filepath.Clean(to)

	prefix := string(lcp.LCP([]byte(from), []byte(to)))

	if prefix == "" {
		return "", errors.New("No prefix in common")
	}

	fromRest := strings.TrimPrefix(from, prefix)
	toRest := strings.TrimPrefix(to, prefix)

	fromPieces := strings.Split(fromRest, string(filepath.Separator))

	dots := make([]string, len(fromPieces))

	for i, _ := range fromPieces {
		dots[i] = ".."
	}

	return filepath.Join(filepath.Join(dots...), toRest), nil

}

func createGoldenTest(fullPkgPath, goldenFolderName string) error {

	//TODO: should this be public?

	pkgName, err := verifyPkgForGolden(fullPkgPath)

	if err != nil {
		return errors.New("Package didn't validate: " + err.Error())
	}

	buf := new(bytes.Buffer)

	err = goldenTestTemplate.Execute(buf, map[string]string{
		"gametype": pkgName,
		"folder":   goldenFolderName,
	})

	if err != nil {
		return errors.New("Couldn't generate blob from template: " + err.Error())
	}

	return ioutil.WriteFile(filepath.Join(fullPkgPath, "golden_test.go"), buf.Bytes(), 0644)

}

//verifyPkgForGolden looks at the given package, returns the package name, and
//verifies that it has a NewDelegate method.
func verifyPkgForGolden(fullPkgName string) (string, error) {
	pkgs, err := parser.ParseDir(token.NewFileSet(), fullPkgName, nil, 0)

	if err != nil {
		return "", errors.New("Couldn't parse folder: " + err.Error())
	}

	if len(pkgs) < 1 {
		return "", errors.New("No packages in that directory")
	}

	if len(pkgs) > 1 {
		return "", errors.New("More than one package in that directory")
	}

	var pkg *ast.Package
	pkgName := ""

	for key, p := range pkgs {
		pkgName = key
		pkg = p
	}

	foundNewDelegate := false

	for _, file := range pkg.Files {
		for _, decl := range file.Decls {
			fun, ok := decl.(*ast.FuncDecl)
			if !ok {
				continue
			}

			if fun.Name.String() != "NewDelegate" {
				continue
			}

			//OK, it might be the function. Does it have the right signature?

			if fun.Recv != nil {
				return "", errors.New("NewDelegate had a receiver")
			}

			if fun.Type.Params.NumFields() > 0 {
				return "", errors.New("NewDelegate took more than 0 items")
			}

			if fun.Type.Results.NumFields() != 1 {
				return "", errors.New("NewDelegate didn't return exactly one item")
			}

			//TODO: check that the returned item implements
			//boardgame.GameDelegate.

			foundNewDelegate = true
			break

		}

		if foundNewDelegate {
			break
		}
	}

	if !foundNewDelegate {
		return "", errors.New("Couldn't find NewDelegate")
	}

	return pkgName, nil

}

var goldenTestTemplateText = `package {{.gametype}}

/*

This file was created automatically by the filesystem storage layer with a
golden folder.

It will be overwritten the next time a filesystem is booted that uses this
game package.

*/

import (
	"github.com/jkomoros/boardgame/util/golden"
	"testing"
)

func TestGolden(t *testing.T) {
	if err := golden.CompareFolder(NewDelegate(), "{{.folder}}"); err != nil {
		t.Error(err.Error())
	}
}
`
<commit_msg>Add a note about vendoring should work for LinkGoldenFolders. Part of #648,.<commit_after>package golden

import (
	"bytes"
	"errors"
	"github.com/abcum/lcp"
	"github.com/jkomoros/boardgame"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"text/template"
)

var goldenTestTemplate *template.Template

func init() {
	goldenTestTemplate = template.Must(template.New("golden").Parse(goldenTestTemplateText))
}

//LinkGoldenFolders helps create a folder system for the `filesystem` storage
//layer, with the individual folders pointing back to folders adjacent to the
//games they're affiliated with. This is useful when you want to generate new
//golden tests for game types. First we use reflection to find the package
//path for each delegate, ensure a folder exists within it with tbe
//goldenFolderName name, create a soft-link from basePath to that folder, and
//create a `golden_test.go` file that automatically tests all of those golden
//files (and assumes that your package defines a `NewDelegate()
//boardgame.GameDelegate` method). The result is that the underlying files
//will be stored in folders adjacent to the games they are relative to, which
//is convenient if you're adding new golden games to the test set. The
//`filesystem` storage layer will call this as a convenience if you pass a
//non-"" goldenFolderName to filesystem.NewStorageManager.
func LinkGoldenFolders(basePath string, goldenFolderName string, managers []*boardgame.GameManager) error {

	if goldenFolderName == "" {
		goldenFolderName = "golden"
	}

	if _, err := os.Stat(basePath); os.IsNotExist(err) {
		return errors.New(basePath + " does not already exist.")
	}

	for _, manager := range managers {
		pkgPath := reflect.ValueOf(manager.Delegate()).Elem().Type().PkgPath()
		if err := linkGoldenFolder(manager.Delegate().Name(), pkgPath, basePath, goldenFolderName); err != nil {
			return errors.New("Couldn't link golden folder for " + manager.Delegate().Name() + ": " + err.Error())
		}
	}

	return nil
}

func linkGoldenFolder(gameType, pkgPath, basePath, goldenFolderName string) error {

	//TODO: should this be public?

	//This SHOULD handle vendored games correctly, given that
	//reflect.PkgPath() returns using the full path, including /vendor/

	goPath := os.Getenv("GOPATH")

	if goPath == "" {
		return errors.New("Gopath wasn't set")
	}

	fullPkgPath := filepath.Join(goPath, "src", pkgPath)

	fullPath := filepath.Join(fullPkgPath, goldenFolderName)

	if _, err := os.Stat(fullPath); os.IsNotExist(err) {
		log.Println("Creating " + fullPath)
		if err := os.Mkdir(fullPath, 0700); err != nil {
			return errors.New("Could not make golden path: " + err.Error())
		}
	}

	gamePath := filepath.Join(basePath, gameType)

	if _, err := os.Stat(gamePath); os.IsNotExist(err) {

		execPath, err := os.Executable()

		if err != nil {
			return errors.New("Couldn't get executable path: " + err.Error())
		}

		relPath, err := relativizePaths(execPath, fullPath)

		if err != nil {
			return errors.New("Couldn't relativize paths: " + err.Error())
		}

		log.Println("Linking " + gamePath + " to " + relPath)

		//Soft link from basePath.

		if err := os.Symlink(relPath, gamePath); err != nil {
			return errors.New("Couldn't create symlink: " + err.Error())
		}
	}

	//TODO: allow this to be skipped as an otpion.
	if err := createGoldenTest(fullPkgPath, goldenFolderName); err != nil {
		return errors.New("Couldn't store golden test: " + err.Error())
	}

	return nil

}

//relativizePaths takes two absolute paths and returns a string that is the
//relative path from from to to.
func relativizePaths(from, to string) (string, error) {

	if !filepath.IsAbs(from) {
		return "", errors.New("From is not absolute")
	}

	if !filepath.IsAbs(to) {
		return "", errors.New("To is not absolute")
	}

	from = filepath.Clean(from)
	to = filepath.Clean(to)

	prefix := string(lcp.LCP([]byte(from), []byte(to)))

	if prefix == "" {
		return "", errors.New("No prefix in common")
	}

	fromRest := strings.TrimPrefix(from, prefix)
	toRest := strings.TrimPrefix(to, prefix)

	fromPieces := strings.Split(fromRest, string(filepath.Separator))

	dots := make([]string, len(fromPieces))

	for i, _ := range fromPieces {
		dots[i] = ".."
	}

	return filepath.Join(filepath.Join(dots...), toRest), nil

}

func createGoldenTest(fullPkgPath, goldenFolderName string) error {

	//TODO: should this be public?

	pkgName, err := verifyPkgForGolden(fullPkgPath)

	if err != nil {
		return errors.New("Package didn't validate: " + err.Error())
	}

	buf := new(bytes.Buffer)

	err = goldenTestTemplate.Execute(buf, map[string]string{
		"gametype": pkgName,
		"folder":   goldenFolderName,
	})

	if err != nil {
		return errors.New("Couldn't generate blob from template: " + err.Error())
	}

	return ioutil.WriteFile(filepath.Join(fullPkgPath, "golden_test.go"), buf.Bytes(), 0644)

}

//verifyPkgForGolden looks at the given package, returns the package name, and
//verifies that it has a NewDelegate method.
func verifyPkgForGolden(fullPkgName string) (string, error) {
	pkgs, err := parser.ParseDir(token.NewFileSet(), fullPkgName, nil, 0)

	if err != nil {
		return "", errors.New("Couldn't parse folder: " + err.Error())
	}

	if len(pkgs) < 1 {
		return "", errors.New("No packages in that directory")
	}

	if len(pkgs) > 1 {
		return "", errors.New("More than one package in that directory")
	}

	var pkg *ast.Package
	pkgName := ""

	for key, p := range pkgs {
		pkgName = key
		pkg = p
	}

	foundNewDelegate := false

	for _, file := range pkg.Files {
		for _, decl := range file.Decls {
			fun, ok := decl.(*ast.FuncDecl)
			if !ok {
				continue
			}

			if fun.Name.String() != "NewDelegate" {
				continue
			}

			//OK, it might be the function. Does it have the right signature?

			if fun.Recv != nil {
				return "", errors.New("NewDelegate had a receiver")
			}

			if fun.Type.Params.NumFields() > 0 {
				return "", errors.New("NewDelegate took more than 0 items")
			}

			if fun.Type.Results.NumFields() != 1 {
				return "", errors.New("NewDelegate didn't return exactly one item")
			}

			//TODO: check that the returned item implements
			//boardgame.GameDelegate.

			foundNewDelegate = true
			break

		}

		if foundNewDelegate {
			break
		}
	}

	if !foundNewDelegate {
		return "", errors.New("Couldn't find NewDelegate")
	}

	return pkgName, nil

}

var goldenTestTemplateText = `package {{.gametype}}

/*

This file was created automatically by the filesystem storage layer with a
golden folder.

It will be overwritten the next time a filesystem is booted that uses this
game package.

*/

import (
	"github.com/jkomoros/boardgame/util/golden"
	"testing"
)

func TestGolden(t *testing.T) {
	if err := golden.CompareFolder(NewDelegate(), "{{.folder}}"); err != nil {
		t.Error(err.Error())
	}
}
`
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"sync"
	"text/tabwriter"
	"time"

	"github.com/codegangsta/cli"
	"github.com/itsabot/abot/core"
	"github.com/itsabot/abot/core/log"
)

var conf *core.PluginJSON

func main() {
	rand.Seed(time.Now().UnixNano())
	log.SetDebug(os.Getenv("ABOT_DEBUG") == "true")

	var err error
	conf, err = core.LoadConf()
	if err != nil {
		log.Fatal(err)
	}

	app := cli.NewApp()
	app.Name = conf.Name
	app.Usage = conf.Description
	app.Version = conf.Version
	app.Commands = []cli.Command{
		{
			Name:    "server",
			Aliases: []string{"s"},
			Usage:   "run server",
			Action: func(c *cli.Context) {
				var err error
				if err = startServer(); err != nil {
					l := log.New("")
					l.SetFlags(0)
					l.Fatalf("could not start server\n%s", err)
				}
			},
		},
		{
			Name:    "plugin",
			Aliases: []string{"p"},
			Usage:   "manage and install plugins from plugins.json",
			Subcommands: []cli.Command{
				{
					Name:  "install",
					Usage: "download and install plugins listed in plugins.json",
					Action: func(c *cli.Context) {
						installPlugins()
					},
				},
				{
					Name:    "search",
					Aliases: []string{"s"},
					Usage:   "search plugins indexed on itsabot.org",
					Action: func(c *cli.Context) {
						l := log.New("")
						l.SetFlags(0)
						args := c.Args()
						if len(args) == 0 || len(args) > 2 {
							l.Fatal(errors.New(`usage: abot plugin search "{term}"`))
						}
						if err := searchPlugins(args.First()); err != nil {
							l.Fatalf("could not start console\n%s", err)
						}
					},
				},
				{
					Name:    "update",
					Aliases: []string{"u", "upgrade"},
					Usage:   "update and install plugins listed in plugins.json",
					Action: func(c *cli.Context) {
						updatePlugins()
					},
				},
			},
		},
		{
			Name:    "console",
			Aliases: []string{"c"},
			Usage:   "communicate with a running abot server",
			Action: func(c *cli.Context) {
				if err := startConsole(c); err != nil {
					l := log.New("")
					l.SetFlags(0)
					l.Fatalf("could not start console\n%s", err)
				}
			},
		},
	}
	app.Action = func(c *cli.Context) {
		cli.ShowAppHelp(c)
	}
	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}

// startServer initializes any clients that are needed, sets up routes, and
// boots plugins.
func startServer() error {
	hr, err := core.NewServer()
	if err != nil {
		return err
	}
	log.Info("started", conf.Name)
	if err = http.ListenAndServe(":"+os.Getenv("PORT"), hr); err != nil {
		return err
	}
	return nil
}

func searchPlugins(query string) error {
	byt, err := searchItsAbot(query)
	if err != nil {
		return err
	}
	if err = outputPluginResults(os.Stdout, byt); err != nil {
		return err
	}
	return nil
}

func outputPluginResults(w io.Writer, byt []byte) error {
	var results []struct {
		ID            uint64
		Name          string
		Username      string
		Description   string
		Path          string
		Readme        string
		DownloadCount uint64
		Similarity    float64
	}
	if err := json.Unmarshal(byt, &results); err != nil {
		return err
	}
	writer := tabwriter.Writer{}
	writer.Init(w, 0, 8, 1, '\t', 0)
	_, err := writer.Write([]byte("NAME\tDESCRIPTION\tUSERNAME\tDOWNLOADS\n"))
	if err != nil {
		return err
	}
	for _, result := range results {
		d := result.Description
		if len(result.Description) >= 30 {
			d = d[:27] + "..."
		}
		_, err = writer.Write([]byte(fmt.Sprintf("%s\t%s\t%s\t%d\n",
			result.Name, d, result.Username, result.DownloadCount)))
		if err != nil {
			return err
		}
	}
	return writer.Flush()
}

func searchItsAbot(q string) ([]byte, error) {
	u := fmt.Sprintf("https://www.itsabot.org/api/search.json?q=%s",
		url.QueryEscape(q))
	client := http.Client{Timeout: time.Duration(10 * time.Second)}
	res, err := client.Get(u)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := res.Body.Close(); err != nil {
			return
		}
	}()
	return ioutil.ReadAll(res.Body)
}

func startConsole(c *cli.Context) error {
	args := c.Args()
	if len(args) == 0 || len(args) >= 3 {
		return errors.New("usage: abot console {abotAddress} {userPhone}")
	}
	var addr, phone string
	if len(args) == 1 {
		addr = "localhost:" + os.Getenv("PORT")
		phone = args[0]
	} else if len(args) == 2 {
		addr = args[0]
		phone = args[1]
	}

	// Capture ^C interrupt to add a newline
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)
	go func() {
		for _ = range sig {
			fmt.Printf("\n")
			os.Exit(0)
		}
	}()

	base := "http://" + addr + "?flexidtype=2&flexid=" + url.QueryEscape(phone) + "&cmd="
	scanner := bufio.NewScanner(os.Stdin)

	// Test connection
	req, err := http.NewRequest("GET", base, nil)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	if err = resp.Body.Close(); err != nil {
		return err
	}
	fmt.Print("> ")

	// Handle each user input
	for scanner.Scan() {
		cmd := scanner.Text()
		req, err := http.NewRequest("POST", base+url.QueryEscape(cmd), nil)
		client := &http.Client{}
		resp, err := client.Do(req)
		if err != nil {
			return err
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		if err = resp.Body.Close(); err != nil {
			return err
		}
		fmt.Println(string(body))
		fmt.Print("> ")
	}
	if err := scanner.Err(); err != nil {
		return err
	}
	return nil
}

func installPlugins() {
	l := log.New("")
	l.SetFlags(0)
	l.SetDebug(os.Getenv("ABOT_DEBUG") == "true")

	plugins := buildPluginFile(l)

	// Fetch all plugins
	l.Info("Fetching", len(plugins.Dependencies), "plugins...")
	outC, err := exec.
		Command("/bin/sh", "-c", "go get ./...").
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}

	// Sync each of them to get dependencies
	var wg sync.WaitGroup
	wg.Add(len(plugins.Dependencies))
	rand.Seed(time.Now().UTC().UnixNano())
	for url, version := range plugins.Dependencies {
		go func(url, version string) {
			// Check out specific commit
			var outB []byte
			if version != "*" {
				l.Debug("checking out", url, "at", version)
				p := filepath.Join(os.Getenv("GOPATH"), "src",
					url)
				c := fmt.Sprintf("git -C %s checkout %s", p, version)
				outB, err = exec.
					Command("/bin/sh", "-c", c).
					CombinedOutput()
				if err != nil {
					l.Debug(string(outB))
					l.Fatal(err)
				}
			}

			// Anonymously increment the plugin's download count
			// at itsabot.org
			l.Debug("incrementing download count", url)
			p := struct{ URL string }{URL: url}
			outB, err = json.Marshal(p)
			if err != nil {
				l.Info("failed to build itsabot.org JSON.", err)
				wg.Done()
				return
			}
			var u string
			if len(os.Getenv("ITSABOT_URL")) > 0 {
				u = os.Getenv("ITSABOT_URL") + "/api/plugins.json"
			} else {
				u = "https://www.itsabot.org/api/plugins.json"
			}
			resp, errB := http.Post(u, "application/json",
				bytes.NewBuffer(outB))
			if errB != nil {
				l.Info("failed to update itsabot.org.", errB)
				wg.Done()
				return
			}
			defer func() {
				if errB = resp.Body.Close(); errB != nil {
					l.Fatal(errB)
				}
			}()
			if resp.StatusCode != 200 {
				l.Infof("WARN: %d - %s\n", resp.StatusCode,
					resp.Status)
			}
			wg.Done()
		}(url, version)
	}
	wg.Wait()

	// Ensure dependencies are still there with the latest checked out
	// versions, and install the plugins
	l.Info("Installing plugins...")
	outC, err = exec.
		Command("/bin/sh", "-c", "go get ./...").
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}

	updateGlockfileAndInstall(l)
	l.Info("Success!")
}

func updatePlugins() {
	l := log.New("")
	l.SetFlags(0)
	l.SetDebug(os.Getenv("ABOT_DEBUG") == "true")

	plugins := buildPluginFile(l)

	l.Info("Updating plugins...")
	for path, version := range plugins.Dependencies {
		if version != "*" {
			continue
		}
		l.Infof("Updating %s...\n", path)
		outC, err := exec.
			Command("/bin/sh", "-c", "go get -u "+path).
			CombinedOutput()
		if err != nil {
			l.Info(string(outC))
			l.Fatal(err)
		}
	}

	updateGlockfileAndInstall(l)
	l.Info("Success!")
}

func updateGlockfileAndInstall(l *log.Logger) {
	outC, err := exec.
		Command("/bin/sh", "-c", `pwd | sed "s|$GOPATH/src/||"`).
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}

	// Update plugin dependency versions in GLOCKFILE
	p := string(outC)
	outC, err = exec.
		Command("/bin/sh", "-c", "glock save "+p).
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}

	outC, err = exec.
		Command("/bin/sh", "-c", "go install").
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}
}

func buildPluginFile(l *log.Logger) *core.PluginJSON {
	plugins, err := core.LoadConf()
	if err != nil {
		l.Fatal(err)
	}

	// Create plugin.go file, truncate if exists
	fi, err := os.Create("plugins.go")
	if err != nil {
		l.Fatal(err)
	}
	defer func() {
		if err = fi.Close(); err != nil {
			l.Fatal(err)
		}
	}()

	// Insert _ imports
	s := "// This file is generated by `abot plugin install`. Do not edit.\n\n"
	s += "package main\n\nimport (\n"
	for url := range plugins.Dependencies {
		s += fmt.Sprintf("\t_ \"%s\"\n", url)
	}
	s += ")"
	_, err = fi.WriteString(s)
	if err != nil {
		l.Fatal(err)
	}

	return plugins
}
<commit_msg>Fix abot console<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"sync"
	"text/tabwriter"
	"time"

	"github.com/codegangsta/cli"
	"github.com/itsabot/abot/core"
	"github.com/itsabot/abot/core/log"
	"github.com/itsabot/abot/shared/datatypes"
)

var conf *core.PluginJSON

func main() {
	rand.Seed(time.Now().UnixNano())
	log.SetDebug(os.Getenv("ABOT_DEBUG") == "true")

	var err error
	conf, err = core.LoadConf()
	if err != nil {
		log.Fatal(err)
	}

	app := cli.NewApp()
	app.Name = conf.Name
	app.Usage = conf.Description
	app.Version = conf.Version
	app.Commands = []cli.Command{
		{
			Name:    "server",
			Aliases: []string{"s"},
			Usage:   "run server",
			Action: func(c *cli.Context) {
				var err error
				if err = startServer(); err != nil {
					l := log.New("")
					l.SetFlags(0)
					l.Fatalf("could not start server\n%s", err)
				}
			},
		},
		{
			Name:    "plugin",
			Aliases: []string{"p"},
			Usage:   "manage and install plugins from plugins.json",
			Subcommands: []cli.Command{
				{
					Name:  "install",
					Usage: "download and install plugins listed in plugins.json",
					Action: func(c *cli.Context) {
						installPlugins()
					},
				},
				{
					Name:    "search",
					Aliases: []string{"s"},
					Usage:   "search plugins indexed on itsabot.org",
					Action: func(c *cli.Context) {
						l := log.New("")
						l.SetFlags(0)
						args := c.Args()
						if len(args) == 0 || len(args) > 2 {
							l.Fatal(errors.New(`usage: abot plugin search "{term}"`))
						}
						if err := searchPlugins(args.First()); err != nil {
							l.Fatalf("could not start console\n%s", err)
						}
					},
				},
				{
					Name:    "update",
					Aliases: []string{"u", "upgrade"},
					Usage:   "update and install plugins listed in plugins.json",
					Action: func(c *cli.Context) {
						updatePlugins()
					},
				},
			},
		},
		{
			Name:    "console",
			Aliases: []string{"c"},
			Usage:   "communicate with a running abot server",
			Action: func(c *cli.Context) {
				if err := startConsole(c); err != nil {
					l := log.New("")
					l.SetFlags(0)
					l.Fatalf("could not start console\n%s", err)
				}
			},
		},
	}
	app.Action = func(c *cli.Context) {
		cli.ShowAppHelp(c)
	}
	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}

// startServer initializes any clients that are needed, sets up routes, and
// boots plugins.
func startServer() error {
	hr, err := core.NewServer()
	if err != nil {
		return err
	}
	log.Info("started", conf.Name)
	if err = http.ListenAndServe(":"+os.Getenv("PORT"), hr); err != nil {
		return err
	}
	return nil
}

func searchPlugins(query string) error {
	byt, err := searchItsAbot(query)
	if err != nil {
		return err
	}
	if err = outputPluginResults(os.Stdout, byt); err != nil {
		return err
	}
	return nil
}

func outputPluginResults(w io.Writer, byt []byte) error {
	var results []struct {
		ID            uint64
		Name          string
		Username      string
		Description   string
		Path          string
		Readme        string
		DownloadCount uint64
		Similarity    float64
	}
	if err := json.Unmarshal(byt, &results); err != nil {
		return err
	}
	writer := tabwriter.Writer{}
	writer.Init(w, 0, 8, 1, '\t', 0)
	_, err := writer.Write([]byte("NAME\tDESCRIPTION\tUSERNAME\tDOWNLOADS\n"))
	if err != nil {
		return err
	}
	for _, result := range results {
		d := result.Description
		if len(result.Description) >= 30 {
			d = d[:27] + "..."
		}
		_, err = writer.Write([]byte(fmt.Sprintf("%s\t%s\t%s\t%d\n",
			result.Name, d, result.Username, result.DownloadCount)))
		if err != nil {
			return err
		}
	}
	return writer.Flush()
}

func searchItsAbot(q string) ([]byte, error) {
	u := fmt.Sprintf("https://www.itsabot.org/api/search.json?q=%s",
		url.QueryEscape(q))
	client := http.Client{Timeout: time.Duration(10 * time.Second)}
	res, err := client.Get(u)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := res.Body.Close(); err != nil {
			return
		}
	}()
	return ioutil.ReadAll(res.Body)
}

func startConsole(c *cli.Context) error {
	args := c.Args()
	if len(args) == 0 || len(args) >= 3 {
		return errors.New("usage: abot console {abotAddress} {userPhone}")
	}
	var addr, phone string
	if len(args) == 1 {
		addr = "localhost:" + os.Getenv("PORT")
		phone = args[0]
	} else if len(args) == 2 {
		addr = args[0]
		phone = args[1]
	}

	// Capture ^C interrupt to add a newline
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)
	go func() {
		for _ = range sig {
			fmt.Printf("\n")
			os.Exit(0)
		}
	}()

	body := struct {
		CMD        string
		FlexID     string
		FlexIDType dt.FlexIDType
	}{
		FlexID:     phone,
		FlexIDType: 2,
	}
	base := "http://" + addr
	scanner := bufio.NewScanner(os.Stdin)

	// Test connection
	req, err := http.NewRequest("GET", base, nil)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	if err = resp.Body.Close(); err != nil {
		return err
	}
	fmt.Print("> ")

	// Handle each user input
	for scanner.Scan() {
		body.CMD = scanner.Text()
		byt, err := json.Marshal(body)
		if err != nil {
			return err
		}
		req, err := http.NewRequest("POST", base, bytes.NewBuffer(byt))
		if err != nil {
			return err
		}
		client := &http.Client{}
		resp, err := client.Do(req)
		if err != nil {
			return err
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		if err = resp.Body.Close(); err != nil {
			return err
		}
		fmt.Println(string(body))
		fmt.Print("> ")
	}
	if err := scanner.Err(); err != nil {
		return err
	}
	return nil
}

func installPlugins() {
	l := log.New("")
	l.SetFlags(0)
	l.SetDebug(os.Getenv("ABOT_DEBUG") == "true")

	plugins := buildPluginFile(l)

	// Fetch all plugins
	l.Info("Fetching", len(plugins.Dependencies), "plugins...")
	outC, err := exec.
		Command("/bin/sh", "-c", "go get ./...").
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}

	// Sync each of them to get dependencies
	var wg sync.WaitGroup
	wg.Add(len(plugins.Dependencies))
	rand.Seed(time.Now().UTC().UnixNano())
	for url, version := range plugins.Dependencies {
		go func(url, version string) {
			// Check out specific commit
			var outB []byte
			if version != "*" {
				l.Debug("checking out", url, "at", version)
				p := filepath.Join(os.Getenv("GOPATH"), "src",
					url)
				c := fmt.Sprintf("git -C %s checkout %s", p, version)
				outB, err = exec.
					Command("/bin/sh", "-c", c).
					CombinedOutput()
				if err != nil {
					l.Debug(string(outB))
					l.Fatal(err)
				}
			}

			// Anonymously increment the plugin's download count
			// at itsabot.org
			l.Debug("incrementing download count", url)
			p := struct{ URL string }{URL: url}
			outB, err = json.Marshal(p)
			if err != nil {
				l.Info("failed to build itsabot.org JSON.", err)
				wg.Done()
				return
			}
			var u string
			if len(os.Getenv("ITSABOT_URL")) > 0 {
				u = os.Getenv("ITSABOT_URL") + "/api/plugins.json"
			} else {
				u = "https://www.itsabot.org/api/plugins.json"
			}
			resp, errB := http.Post(u, "application/json",
				bytes.NewBuffer(outB))
			if errB != nil {
				l.Info("failed to update itsabot.org.", errB)
				wg.Done()
				return
			}
			defer func() {
				if errB = resp.Body.Close(); errB != nil {
					l.Fatal(errB)
				}
			}()
			if resp.StatusCode != 200 {
				l.Infof("WARN: %d - %s\n", resp.StatusCode,
					resp.Status)
			}
			wg.Done()
		}(url, version)
	}
	wg.Wait()

	// Ensure dependencies are still there with the latest checked out
	// versions, and install the plugins
	l.Info("Installing plugins...")
	outC, err = exec.
		Command("/bin/sh", "-c", "go get ./...").
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}

	updateGlockfileAndInstall(l)
	l.Info("Success!")
}

func updatePlugins() {
	l := log.New("")
	l.SetFlags(0)
	l.SetDebug(os.Getenv("ABOT_DEBUG") == "true")

	plugins := buildPluginFile(l)

	l.Info("Updating plugins...")
	for path, version := range plugins.Dependencies {
		if version != "*" {
			continue
		}
		l.Infof("Updating %s...\n", path)
		outC, err := exec.
			Command("/bin/sh", "-c", "go get -u "+path).
			CombinedOutput()
		if err != nil {
			l.Info(string(outC))
			l.Fatal(err)
		}
	}

	updateGlockfileAndInstall(l)
	l.Info("Success!")
}

func updateGlockfileAndInstall(l *log.Logger) {
	outC, err := exec.
		Command("/bin/sh", "-c", `pwd | sed "s|$GOPATH/src/||"`).
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}

	// Update plugin dependency versions in GLOCKFILE
	p := string(outC)
	outC, err = exec.
		Command("/bin/sh", "-c", "glock save "+p).
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}

	outC, err = exec.
		Command("/bin/sh", "-c", "go install").
		CombinedOutput()
	if err != nil {
		l.Info(string(outC))
		l.Fatal(err)
	}
}

func buildPluginFile(l *log.Logger) *core.PluginJSON {
	plugins, err := core.LoadConf()
	if err != nil {
		l.Fatal(err)
	}

	// Create plugin.go file, truncate if exists
	fi, err := os.Create("plugins.go")
	if err != nil {
		l.Fatal(err)
	}
	defer func() {
		if err = fi.Close(); err != nil {
			l.Fatal(err)
		}
	}()

	// Insert _ imports
	s := "// This file is generated by `abot plugin install`. Do not edit.\n\n"
	s += "package main\n\nimport (\n"
	for url := range plugins.Dependencies {
		s += fmt.Sprintf("\t_ \"%s\"\n", url)
	}
	s += ")"
	_, err = fi.WriteString(s)
	if err != nil {
		l.Fatal(err)
	}

	return plugins
}
<|endoftext|>


<commit_before>// Copyright 2019 The LUCI Authors.
//
// 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 deployer

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"sync"
	"testing"

	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/logging/gologger"

	"go.chromium.org/luci/cipd/client/cipd/fs"
	"go.chromium.org/luci/cipd/client/cipd/pkg"
)

func TestMultipleDeployProcesses(t *testing.T) {
	const procs = 30

	tempDir, err := ioutil.TempDir("", "cipd_test")
	if err != nil {
		t.Fatalf("TempDir: %s", err)
	}
	t.Cleanup(func() { os.RemoveAll(tempDir) })

	wg := sync.WaitGroup{}
	wg.Add(procs)
	for p := 0; p < procs; p++ {
		p := p
		go func() {
			defer wg.Done()
			// Run a crashing process first, to emulate an unowned lock.
			_, err := runDeployerProc(p, tempDir, true)
			if err == nil {
				t.Errorf("Subprocess %d unexpectedly succeeded", p)
				return
			}
			// Now run a non-crashing deployer, it should succeed.
			out, err := runDeployerProc(p, tempDir, false)
			if err != nil {
				t.Errorf("Subprocess %d failed (%s):\n%s", p, err, out)
				return
			}
		}()
	}
	wg.Wait()
}

func runDeployerProc(idx int, tempDir string, crash bool) (out []byte, err error) {
	// See https://npf.io/2015/06/testing-exec-command/
	cmd := exec.Command(os.Args[0], "-test.run=TestDeployHelperProcess")
	cmd.Env = append(os.Environ(),
		fmt.Sprintf("CIPD_TEST_PROC_IDX=%d", idx),
		fmt.Sprintf("CIPD_TEST_TEMP_DIR=%s", tempDir),
		fmt.Sprintf("CIPD_TEST_CRASH=%v", crash),
	)
	return cmd.CombinedOutput()
}

func TestDeployHelperProcess(t *testing.T) {
	idxs := os.Getenv("CIPD_TEST_PROC_IDX")
	if idxs == "" {
		t.Skip("Skipping the helper test")
	}
	idx, err := strconv.ParseInt(idxs, 10, 64)
	if err != nil {
		t.Fatalf("Bad CIPD_TEST_PROC_IDX %q", idxs)
	}
	tempDir := os.Getenv("CIPD_TEST_TEMP_DIR")
	if tempDir == "" {
		t.Fatalf("Bad empty CIPD_TEST_TEMP_DIR")
	}
	crash := os.Getenv("CIPD_TEST_CRASH") == "true"

	// Do a hard crash right after acquiring the FS lock.
	if crash {
		origLockFS := lockFS
		lockFS = func(path string, waiter func() error) (func() error, error) {
			origLockFS(path, waiter)
			os.Exit(1)
			panic("unreachable")
		}
	}

	installMode := pkg.InstallModeSymlink
	if runtime.GOOS == "windows" {
		installMode = pkg.InstallModeCopy
	}

	// Verbose logging to stderr.
	ctx := gologger.StdConfig.Use(context.Background())
	ctx = logging.SetLevel(ctx, logging.Debug)

	// Do multiple rounds to increase the chance of collision.
	for r := 0; r < 20; r++ {
		instID := (int(idx) + r) % 5

		logging.Infof(ctx, "------------------------------------------------------")
		logging.Infof(ctx, "Round %d", r)
		logging.Infof(ctx, "------------------------------------------------------")

		inst := makeTestInstance(fmt.Sprintf("pkg/%d", instID), []fs.File{
			fs.NewTestFile(fmt.Sprintf("private_%d", instID), "data", fs.TestFileOpts{}),
			fs.NewTestFile("path/shared", "data", fs.TestFileOpts{}),
		}, installMode)
		inst.instanceID = fmt.Sprintf("-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwF%02d", instID)

		d := New(tempDir)
		maxThreads := 16
		_, err = d.DeployInstance(ctx, "", inst, maxThreads)
		if err != nil {
			t.Fatalf("DeployInstance failed on round %d: %s", r, err)
		}
	}
}
<commit_msg>[cipd] Disable flaky test.<commit_after>// Copyright 2019 The LUCI Authors.
//
// 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 deployer

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"sync"
	"testing"

	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/logging/gologger"

	"go.chromium.org/luci/cipd/client/cipd/fs"
	"go.chromium.org/luci/cipd/client/cipd/pkg"
)

func TestMultipleDeployProcesses(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skipf("Skipping, flaky on luci-go-continuous-win7-64: crbug.com/1077343")
	}

	const procs = 30

	tempDir, err := ioutil.TempDir("", "cipd_test")
	if err != nil {
		t.Fatalf("TempDir: %s", err)
	}
	t.Cleanup(func() { os.RemoveAll(tempDir) })

	wg := sync.WaitGroup{}
	wg.Add(procs)
	for p := 0; p < procs; p++ {
		p := p
		go func() {
			defer wg.Done()
			// Run a crashing process first, to emulate an unowned lock.
			_, err := runDeployerProc(p, tempDir, true)
			if err == nil {
				t.Errorf("Subprocess %d unexpectedly succeeded", p)
				return
			}
			// Now run a non-crashing deployer, it should succeed.
			out, err := runDeployerProc(p, tempDir, false)
			if err != nil {
				t.Errorf("Subprocess %d failed (%s):\n%s", p, err, out)
				return
			}
		}()
	}
	wg.Wait()
}

func runDeployerProc(idx int, tempDir string, crash bool) (out []byte, err error) {
	// See https://npf.io/2015/06/testing-exec-command/
	cmd := exec.Command(os.Args[0], "-test.run=TestDeployHelperProcess")
	cmd.Env = append(os.Environ(),
		fmt.Sprintf("CIPD_TEST_PROC_IDX=%d", idx),
		fmt.Sprintf("CIPD_TEST_TEMP_DIR=%s", tempDir),
		fmt.Sprintf("CIPD_TEST_CRASH=%v", crash),
	)
	return cmd.CombinedOutput()
}

func TestDeployHelperProcess(t *testing.T) {
	idxs := os.Getenv("CIPD_TEST_PROC_IDX")
	if idxs == "" {
		t.Skip("Skipping the helper test")
	}
	idx, err := strconv.ParseInt(idxs, 10, 64)
	if err != nil {
		t.Fatalf("Bad CIPD_TEST_PROC_IDX %q", idxs)
	}
	tempDir := os.Getenv("CIPD_TEST_TEMP_DIR")
	if tempDir == "" {
		t.Fatalf("Bad empty CIPD_TEST_TEMP_DIR")
	}
	crash := os.Getenv("CIPD_TEST_CRASH") == "true"

	// Do a hard crash right after acquiring the FS lock.
	if crash {
		origLockFS := lockFS
		lockFS = func(path string, waiter func() error) (func() error, error) {
			origLockFS(path, waiter)
			os.Exit(1)
			panic("unreachable")
		}
	}

	installMode := pkg.InstallModeSymlink
	if runtime.GOOS == "windows" {
		installMode = pkg.InstallModeCopy
	}

	// Verbose logging to stderr.
	ctx := gologger.StdConfig.Use(context.Background())
	ctx = logging.SetLevel(ctx, logging.Debug)

	// Do multiple rounds to increase the chance of collision.
	for r := 0; r < 20; r++ {
		instID := (int(idx) + r) % 5

		logging.Infof(ctx, "------------------------------------------------------")
		logging.Infof(ctx, "Round %d", r)
		logging.Infof(ctx, "------------------------------------------------------")

		inst := makeTestInstance(fmt.Sprintf("pkg/%d", instID), []fs.File{
			fs.NewTestFile(fmt.Sprintf("private_%d", instID), "data", fs.TestFileOpts{}),
			fs.NewTestFile("path/shared", "data", fs.TestFileOpts{}),
		}, installMode)
		inst.instanceID = fmt.Sprintf("-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwF%02d", instID)

		d := New(tempDir)
		maxThreads := 16
		_, err = d.DeployInstance(ctx, "", inst, maxThreads)
		if err != nil {
			t.Fatalf("DeployInstance failed on round %d: %s", r, err)
		}
	}
}
<|endoftext|>


<commit_before>package gocli

import (
	"strconv"
	"regexp"
	"strings"
	"fmt"
)

const (
	STRING = "string"
	INTEGER = "int"
	BOOL = "bool"
)

var (
	re = regexp.MustCompile("^([\\-]+.*)")
)

type Args struct {
	Args []string
	Attributes map[string][]string
	currentKey string
	FlagMap map[string]*Flag
	Flags []*Flag
}

type Flag struct {
	Type string
	Keys []string
	Required bool
	DefaultValue string
	Description string
}

func (f *Flag) Matches(key string) bool {
	for _, k := range f.Keys {
		if strings.HasPrefix(k, key) {
			return true
		}
	}
	return false
}

func (a *Args) RegisterFlag(flag *Flag) {
	if a.FlagMap == nil {
		a.FlagMap = make(map[string]*Flag)
	}
	for _, key := range flag.Keys {
		a.FlagMap[key] = flag
	}
	a.Flags = append(a.Flags, flag)
}

func (a *Args) RegisterString(key string, required bool, defaultValue, description string) {
	a.RegisterFlag(
		&Flag{
			Type: STRING,
			Keys: []string { key },
			Required: required,
			DefaultValue: defaultValue,
			Description: description,
		},
	)
}

func (a *Args) RegisterInt(key string, required bool, defaultValue int, description string) {
	a.RegisterFlag(
		&Flag{
			Type: INTEGER,
			Keys: []string { key },
			Required: required,
			DefaultValue: strconv.Itoa(defaultValue),
			Description: description,
		},
	)
}

func (a *Args) RegisterBool(key string, required bool, defaultValue bool, description string) {
	a.RegisterFlag(
		&Flag{
			Type: STRING,
			Keys: []string { key },
			Required: required,
			DefaultValue: strconv.FormatBool(required),
			Description: description,
		},
	)
}

func (f *Flag) Usage() []string {
	parts := make([]string, 3)
	parts[0] = strings.Join(f.Keys, ", ")
	if f.Required {
		parts[1] = "REQUIRED"
	} else {
		parts[1] = fmt.Sprintf("DEFAULT: %q", f.DefaultValue)
	}
	parts[2] = f.Description

	return parts
}

func (a *Args) Usage() string {
	table := NewTable()
	table.Separator = " "
	for _, flag := range a.Flags {
		table.Add(flag.Usage()...)
	}
	return table.String()
}

func (a *Args) lookup(key string) (flags []*Flag) {
	for i := range a.Flags {
		arg := a.Flags[i]
		if arg.Matches(key) {
			flags = append(flags, arg)
		}
	}
	return flags
}

func (a *Args) Length() int {
	return len(a.Args)
}

func (a *Args) String(key string) {
	a.AddFlag(key, STRING)
}

func (a *Args) Bool(key string) {
	a.AddFlag(key, BOOL)
}

func (a *Args) AddFlag(key, value string) {
	a.RegisterFlag(&Flag{Type: value, Keys: []string { key }})
}

func (a *Args) AddAttribute(k, v string) {
	if a.Attributes == nil {
		a.Attributes = map[string][]string{}
	}
	a.Attributes[k] = append(a.Attributes[k], v)
}

func (a *Args) Parse(args []string) error {
	a.Args = make([]string, 0, 10)
	a.Attributes = make(map[string][]string)
	for _, arg := range args {
		if e := a.handleArg(arg); e != nil {
			return e
		}
	}
	return nil
}

func (a *Args) TypeOf(key string) (out string, e error) {
	flags := a.lookup(key)
	switch len(flags) {
		case 0:
			e = fmt.Errorf("no mapping defined for %s", key)
		case 1:
			out = flags[0].Type
		default:
			e = fmt.Errorf("mapping for %s not uniq", key)
	}
	return out, e
}

func (a *Args) handleArgFlag(flag string) error {
	if t, e := a.TypeOf(flag); e != nil {
		return e
	} else {
		switch t {
			case STRING, INTEGER:
				a.currentKey = flag
			case BOOL:
				a.AddAttribute(flag, "true")
			default:
				return fmt.Errorf("no mapping defined for %s", flag)
		}
	}
	return nil
}

func (a *Args) handleArg(arg string ) error {
	if parts := re.FindStringSubmatch(arg); len(parts) == 2 {
		chunks := strings.Split(parts[1], "=")
		if len(chunks) == 2 {
			key, value := chunks[0], chunks[1]
			a.AddAttribute(key, value)
			return nil
		} else {
			if e := a.handleArgFlag(chunks[0]); e != nil {
				return e
			}
			return nil
		}
	} else if a.currentKey != "" {
		a.AddAttribute(a.currentKey, arg)
		a.currentKey = ""
		return nil
	}
	a.Args = append(a.Args, arg)
	return nil
}

func (a *Args) Get(key string) ([]string) {
	return a.Attributes[key]
}

func (a *Args) GetInt(key string) (int, error) {
	s, e := a.GetString(key)
	if e != nil {
		return 0, e
	}
	return strconv.Atoi(s)
}

func (a *Args) GetString(key string) (string, error) {
	flag, ok := a.FlagMap[key]
	if !ok {
		return "", fmt.Errorf("no mapping defined for %s", key)
	}
	values := a.Get(key)
	if len(values) > 0 {
		return values[len(values)-1], nil
	}
	if flag.Required {
		return "", fmt.Errorf("flag %s is required", key)
	}
	return flag.DefaultValue, nil
}

func (a *Args) GetBool(key string) bool {
	args := a.Attributes[key]
	return (len(args) == 1 && args[0] == "true")
}
<commit_msg>add MustGetString<commit_after>package gocli

import (
	"strconv"
	"regexp"
	"strings"
	"fmt"
)

const (
	STRING = "string"
	INTEGER = "int"
	BOOL = "bool"
)

var (
	re = regexp.MustCompile("^([\\-]+.*)")
)

type Args struct {
	Args []string
	Attributes map[string][]string
	currentKey string
	FlagMap map[string]*Flag
	Flags []*Flag
}

type Flag struct {
	Type string
	Keys []string
	Required bool
	DefaultValue string
	Description string
}

func (f *Flag) Matches(key string) bool {
	for _, k := range f.Keys {
		if strings.HasPrefix(k, key) {
			return true
		}
	}
	return false
}

func (a *Args) RegisterFlag(flag *Flag) {
	if a.FlagMap == nil {
		a.FlagMap = make(map[string]*Flag)
	}
	for _, key := range flag.Keys {
		a.FlagMap[key] = flag
	}
	a.Flags = append(a.Flags, flag)
}

func (a *Args) RegisterString(key string, required bool, defaultValue, description string) {
	a.RegisterFlag(
		&Flag{
			Type: STRING,
			Keys: []string { key },
			Required: required,
			DefaultValue: defaultValue,
			Description: description,
		},
	)
}

func (a *Args) RegisterInt(key string, required bool, defaultValue int, description string) {
	a.RegisterFlag(
		&Flag{
			Type: INTEGER,
			Keys: []string { key },
			Required: required,
			DefaultValue: strconv.Itoa(defaultValue),
			Description: description,
		},
	)
}

func (a *Args) RegisterBool(key string, required bool, defaultValue bool, description string) {
	a.RegisterFlag(
		&Flag{
			Type: STRING,
			Keys: []string { key },
			Required: required,
			DefaultValue: strconv.FormatBool(required),
			Description: description,
		},
	)
}

func (f *Flag) Usage() []string {
	parts := make([]string, 3)
	parts[0] = strings.Join(f.Keys, ", ")
	if f.Required {
		parts[1] = "REQUIRED"
	} else {
		parts[1] = fmt.Sprintf("DEFAULT: %q", f.DefaultValue)
	}
	parts[2] = f.Description

	return parts
}

func (a *Args) Usage() string {
	table := NewTable()
	table.Separator = " "
	for _, flag := range a.Flags {
		table.Add(flag.Usage()...)
	}
	return table.String()
}

func (a *Args) lookup(key string) (flags []*Flag) {
	for i := range a.Flags {
		arg := a.Flags[i]
		if arg.Matches(key) {
			flags = append(flags, arg)
		}
	}
	return flags
}

func (a *Args) Length() int {
	return len(a.Args)
}

func (a *Args) String(key string) {
	a.AddFlag(key, STRING)
}

func (a *Args) Bool(key string) {
	a.AddFlag(key, BOOL)
}

func (a *Args) AddFlag(key, value string) {
	a.RegisterFlag(&Flag{Type: value, Keys: []string { key }})
}

func (a *Args) AddAttribute(k, v string) {
	if a.Attributes == nil {
		a.Attributes = map[string][]string{}
	}
	a.Attributes[k] = append(a.Attributes[k], v)
}

func (a *Args) Parse(args []string) error {
	a.Args = make([]string, 0, 10)
	a.Attributes = make(map[string][]string)
	for _, arg := range args {
		if e := a.handleArg(arg); e != nil {
			return e
		}
	}
	return nil
}

func (a *Args) TypeOf(key string) (out string, e error) {
	flags := a.lookup(key)
	switch len(flags) {
		case 0:
			e = fmt.Errorf("no mapping defined for %s", key)
		case 1:
			out = flags[0].Type
		default:
			e = fmt.Errorf("mapping for %s not uniq", key)
	}
	return out, e
}

func (a *Args) handleArgFlag(flag string) error {
	if t, e := a.TypeOf(flag); e != nil {
		return e
	} else {
		switch t {
			case STRING, INTEGER:
				a.currentKey = flag
			case BOOL:
				a.AddAttribute(flag, "true")
			default:
				return fmt.Errorf("no mapping defined for %s", flag)
		}
	}
	return nil
}

func (a *Args) handleArg(arg string ) error {
	if parts := re.FindStringSubmatch(arg); len(parts) == 2 {
		chunks := strings.Split(parts[1], "=")
		if len(chunks) == 2 {
			key, value := chunks[0], chunks[1]
			a.AddAttribute(key, value)
			return nil
		} else {
			if e := a.handleArgFlag(chunks[0]); e != nil {
				return e
			}
			return nil
		}
	} else if a.currentKey != "" {
		a.AddAttribute(a.currentKey, arg)
		a.currentKey = ""
		return nil
	}
	a.Args = append(a.Args, arg)
	return nil
}

func (a *Args) Get(key string) ([]string) {
	return a.Attributes[key]
}

func (a *Args) GetInt(key string) (int, error) {
	s, e := a.GetString(key)
	if e != nil {
		return 0, e
	}
	return strconv.Atoi(s)
}

func (a *Args) MustGetString(key string) string {
	s, e := a.GetString(key)
	if e != nil {
		panic(e.Error())
	}
	return s
}

func (a *Args) GetString(key string) (string, error) {
	flag, ok := a.FlagMap[key]
	if !ok {
		return "", fmt.Errorf("no mapping defined for %s", key)
	}
	values := a.Get(key)
	if len(values) > 0 {
		return values[len(values)-1], nil
	}
	if flag.Required {
		return "", fmt.Errorf("flag %s is required", key)
	}
	return flag.DefaultValue, nil
}

func (a *Args) GetBool(key string) bool {
	args := a.Attributes[key]
	return (len(args) == 1 && args[0] == "true")
}
<|endoftext|>


<commit_before>package command

import (
	"bytes"
	"fmt"
	"reflect"

	"github.com/pkg/errors"
	"github.com/ungerik/go-reflection"
)

type StringArgsFunc func(args ...string) error
type StringMapArgsFunc func(args map[string]string) error

type StringArgsResultValuesFunc func(args []string) ([]reflect.Value, error)
type StringMapArgsResultValuesFunc func(args map[string]string) ([]reflect.Value, error)

type Args interface {
	NumArgs() int
	ArgName(index int) string
	ArgDescription(index int) string
	ArgTag(index int, tag string) string
	ArgType(index int) reflect.Type
	String() string
}

type ArgsImpl interface {
	Init(outerArgs Args) error
	StringArgsFunc(commandFunc interface{}, resultsHandlers []ResultsHandler) (StringArgsFunc, error)
	StringMapArgsFunc(commandFunc interface{}, resultsHandlers []ResultsHandler) (StringMapArgsFunc, error)
	StringArgsResultValuesFunc(commandFunc interface{}) (StringArgsResultValuesFunc, error)
	StringMapArgsResultValuesFunc(commandFunc interface{}) (StringMapArgsResultValuesFunc, error)
}

func GetStringArgsFunc(commandFunc interface{}, args Args, resultsHandlers ...ResultsHandler) (StringArgsFunc, error) {
	// Note: here happens something unexpected!
	// args implements the Args interface with ArgsDef.
	// This looks like a virtual method call, but of course it is not.
	// The first args is interpreted as (*ArgsDef) to do the method call.
	// We can't use that to get the type that embedds ArgsDef,
	// because ArgsDef knows nothing about the outer embedding type.
	// But args, the first argument to the method, has all the type information,
	// because here the complete outer embedding struct is passed.
	argsImpl := args.(ArgsImpl)
	err := argsImpl.Init(args)
	if err != nil {
		return nil, err
	}
	return argsImpl.StringArgsFunc(commandFunc, resultsHandlers)
}

func MustGetStringArgsFunc(commandFunc interface{}, args Args, resultsHandlers ...ResultsHandler) StringArgsFunc {
	f, err := GetStringArgsFunc(commandFunc, args, resultsHandlers...)
	if err != nil {
		panic(err)
	}
	return f
}

func GetStringMapArgsFunc(commandFunc interface{}, args Args, resultsHandlers ...ResultsHandler) (StringMapArgsFunc, error) {
	// Note: here happens something unexpected!
	// args implements the Args interface with ArgsDef.
	// This looks like a virtual method call, but of course it is not.
	// The first args is interpreted as (*ArgsDef) to do the method call.
	// We can't use that to get the type that embedds ArgsDef,
	// because ArgsDef knows nothing about the outer embedding type.
	// But args, the first argument to the method, has all the type information,
	// because here the complete outer embedding struct is passed.
	argsImpl := args.(ArgsImpl)
	err := argsImpl.Init(args)
	if err != nil {
		return nil, err
	}
	return argsImpl.StringMapArgsFunc(commandFunc, resultsHandlers)
}

func MustGetStringMapArgsFunc(commandFunc interface{}, args Args, resultsHandlers ...ResultsHandler) StringMapArgsFunc {
	f, err := GetStringMapArgsFunc(commandFunc, args, resultsHandlers...)
	if err != nil {
		panic(err)
	}
	return f
}

func GetStringArgsResultValuesFunc(commandFunc interface{}, args Args) (StringArgsResultValuesFunc, error) {
	// Note: here happens something unexpected!
	// args implements the Args interface with ArgsDef.
	// This looks like a virtual method call, but of course it is not.
	// The first args is interpreted as (*ArgsDef) to do the method call.
	// We can't use that to get the type that embedds ArgsDef,
	// because ArgsDef knows nothing about the outer embedding type.
	// But args, the first argument to the method, has all the type information,
	// because here the complete outer embedding struct is passed.
	argsImpl := args.(ArgsImpl)
	err := argsImpl.Init(args)
	if err != nil {
		return nil, err
	}
	return argsImpl.StringArgsResultValuesFunc(commandFunc)
}

func MustGetStringArgsResultValuesFunc(commandFunc interface{}, args Args) StringArgsResultValuesFunc {
	f, err := GetStringArgsResultValuesFunc(commandFunc, args)
	if err != nil {
		panic(err)
	}
	return f
}

func GetStringMapArgsResultValuesFunc(commandFunc interface{}, args Args) (StringMapArgsResultValuesFunc, error) {
	// Note: here happens something unexpected!
	// args implements the Args interface with ArgsDef.
	// This looks like a virtual method call, but of course it is not.
	// The first args is interpreted as (*ArgsDef) to do the method call.
	// We can't use that to get the type that embedds ArgsDef,
	// because ArgsDef knows nothing about the outer embedding type.
	// But args, the first argument to the method, has all the type information,
	// because here the complete outer embedding struct is passed.
	argsImpl := args.(ArgsImpl)
	err := argsImpl.Init(args)
	if err != nil {
		return nil, err
	}
	return argsImpl.StringMapArgsResultValuesFunc(commandFunc)
}

func MustGetStringMapArgsResultValuesFunc(commandFunc interface{}, args Args) StringMapArgsResultValuesFunc {
	f, err := GetStringMapArgsResultValuesFunc(commandFunc, args)
	if err != nil {
		panic(err)
	}
	return f
}

var WithoutArgs ArgsDef

type ArgsDef struct {
	outerArgs       Args
	outerStructType reflect.Type
	argStructFields []reflection.StructFieldName
	initialized     bool
}

func (def *ArgsDef) NumArgs() int {
	return len(def.argStructFields)
}

func (def *ArgsDef) ArgName(index int) string {
	return def.argStructFields[index].Name
}

func (def *ArgsDef) ArgDescription(index int) string {
	return def.ArgTag(index, ArgDescriptionTag)
}

func (def *ArgsDef) ArgTag(index int, tag string) string {
	return def.argStructFields[index].Field.Tag.Get(tag)
}

func (def *ArgsDef) ArgType(index int) reflect.Type {
	return def.argStructFields[index].Field.Type
}

func (def *ArgsDef) String() string {
	if !def.initialized {
		return "ArgsDef not initialized"
	}
	var buf bytes.Buffer
	for _, f := range def.argStructFields {
		if buf.Len() > 0 {
			buf.WriteByte(' ')
		}
		fmt.Fprintf(&buf, "<%s:%s>", f.Name, f.Field.Type)
	}
	return buf.String()
}

func (def *ArgsDef) Init(outerArgs Args) error {
	if def.initialized {
		return nil
	}
	def.outerStructType = reflection.DerefType(reflect.TypeOf(outerArgs))
	if def.outerStructType.Kind() != reflect.Struct {
		return errors.Errorf("ArgsDef must be contained in a struct, but outer type is %s", def.outerStructType)
	}
	def.argStructFields = reflection.FlatExportedStructFieldNames(def.outerStructType, ArgNameTag)
	def.initialized = true
	return nil
}

func (def *ArgsDef) checkFunctionSignature(commandFunc interface{}) (commandFuncVal reflect.Value, numArgs, errorIndex int, err error) {
	commandFuncVal = reflect.ValueOf(commandFunc)
	commandFuncType := commandFuncVal.Type()
	if commandFuncType.Kind() != reflect.Func {
		return reflect.Value{}, -1, -1, errors.Errorf("expected a function or method, but got %s", commandFuncType)
	}

	numResults := commandFuncType.NumOut()
	if numResults > 0 && commandFuncType.Out(numResults-1) == reflection.TypeOfError {
		errorIndex = numResults - 1
	} else {
		errorIndex = -1
	}

	numArgs = len(def.argStructFields)
	if numArgs != commandFuncType.NumIn() {
		return reflect.Value{}, -1, -1, errors.Errorf("number of fields in command.Args struct (%d) does not match number of function arguments (%d)", numArgs, commandFuncType.NumIn())
	}
	for i := range def.argStructFields {
		if def.argStructFields[i].Field.Type != commandFuncType.In(i) {
			return reflect.Value{}, -1, -1, errors.Errorf(
				"type of command.Args struct field '%s' is %s, which does not match function argument %d type %s",
				def.argStructFields[i].Field.Name,
				def.argStructFields[i].Field.Type,
				i,
				commandFuncType.In(i),
			)
		}
	}

	return commandFuncVal, numArgs, errorIndex, nil
}

func (def *ArgsDef) getStringArgsVals(numArgs int, args []string) ([]reflect.Value, error) {
	// Allocate a new args struct because we need addressable
	// variables of struct field types to hold arg values.
	// Instead of new individual variable use fields of args struct.
	argsStruct := reflect.New(def.outerStructType).Elem()
	argVals := make([]reflect.Value, numArgs)
	numStringArgs := len(args)
	for i := range argVals {
		argVals[i] = argsStruct.FieldByIndex(def.argStructFields[i].Field.Index)
		if i >= numStringArgs {
			continue
		}
		err := assignString(argVals[i], args[i])
		if err != nil {
			return nil, err
		}
	}
	return argVals, nil
}

func (def *ArgsDef) getStringMapArgsVals(numArgs int, args map[string]string) ([]reflect.Value, error) {
	// Allocate a new args struct because we need addressable
	// variables of struct field types to hold arg values.
	// Instead of new individual variable use fields of args struct.
	argsStruct := reflect.New(def.outerStructType).Elem()
	argVals := make([]reflect.Value, numArgs)
	for i := range argVals {
		argVals[i] = argsStruct.FieldByIndex(def.argStructFields[i].Field.Index)
		name := def.argStructFields[i].Name
		stringArg, hasArg := args[name]
		if !hasArg {
			continue
		}
		err := assignString(argVals[i], stringArg)
		if err != nil {
			return nil, err
		}
	}
	return argVals, nil
}

func (def *ArgsDef) StringArgsFunc(commandFunc interface{}, resultsHandlers []ResultsHandler) (StringArgsFunc, error) {
	commandFuncVal, numArgs, errorIndex, err := def.checkFunctionSignature(commandFunc)
	if err != nil {
		return nil, err
	}

	return func(args ...string) error {
		argVals, err := def.getStringArgsVals(numArgs, args)
		if err != nil {
			return err
		}

		resultVals := commandFuncVal.Call(argVals)

		var resultErr error
		if errorIndex != -1 {
			resultErr, _ = resultVals[errorIndex].Interface().(error)
			resultVals = resultVals[:errorIndex]
		}
		for _, resultsHandler := range resultsHandlers {
			err = resultsHandler.HandleResults(def.outerArgs, argVals, resultVals, resultErr)
			if err != nil && err != resultErr {
				return err
			}
		}

		return nil
	}, nil
}

func (def *ArgsDef) StringMapArgsFunc(commandFunc interface{}, resultsHandlers []ResultsHandler) (StringMapArgsFunc, error) {
	commandFuncVal, numArgs, errorIndex, err := def.checkFunctionSignature(commandFunc)
	if err != nil {
		return nil, err
	}

	return func(args map[string]string) (err error) {
		argVals, err := def.getStringMapArgsVals(numArgs, args)
		if err != nil {
			return err
		}

		resultVals := commandFuncVal.Call(argVals)

		var resultErr error
		if errorIndex != -1 {
			resultErr, _ = resultVals[errorIndex].Interface().(error)
			resultVals = resultVals[:errorIndex]
		}
		for _, resultsHandler := range resultsHandlers {
			err = resultsHandler.HandleResults(def.outerArgs, argVals, resultVals, resultErr)
			if err != nil && err != resultErr {
				return err
			}
		}

		return nil
	}, nil
}

func (def *ArgsDef) StringArgsResultValuesFunc(commandFunc interface{}) (StringArgsResultValuesFunc, error) {
	commandFuncVal, numArgs, errorIndex, err := def.checkFunctionSignature(commandFunc)
	if err != nil {
		return nil, err
	}

	return func(args []string) ([]reflect.Value, error) {
		argVals, err := def.getStringArgsVals(numArgs, args)
		if err != nil {
			return nil, err
		}

		resultVals := commandFuncVal.Call(argVals)

		var resultErr error
		if errorIndex != -1 {
			resultErr, _ = resultVals[errorIndex].Interface().(error)
			resultVals = resultVals[:errorIndex]
		}
		return resultVals, resultErr
	}, nil
}

func (def *ArgsDef) StringMapArgsResultValuesFunc(commandFunc interface{}) (StringMapArgsResultValuesFunc, error) {
	commandFuncVal, numArgs, errorIndex, err := def.checkFunctionSignature(commandFunc)
	if err != nil {
		return nil, err
	}

	return func(args map[string]string) ([]reflect.Value, error) {
		argVals, err := def.getStringMapArgsVals(numArgs, args)
		if err != nil {
			return nil, err
		}

		resultVals := commandFuncVal.Call(argVals)

		var resultErr error
		if errorIndex != -1 {
			resultErr, _ = resultVals[errorIndex].Interface().(error)
			resultVals = resultVals[:errorIndex]
		}
		return resultVals, resultErr
	}, nil
}
<commit_msg>deref pointer in ArgsDef.String()<commit_after>package command

import (
	"bytes"
	"fmt"
	"reflect"

	"github.com/pkg/errors"
	"github.com/ungerik/go-reflection"
)

type StringArgsFunc func(args ...string) error
type StringMapArgsFunc func(args map[string]string) error

type StringArgsResultValuesFunc func(args []string) ([]reflect.Value, error)
type StringMapArgsResultValuesFunc func(args map[string]string) ([]reflect.Value, error)

type Args interface {
	NumArgs() int
	ArgName(index int) string
	ArgDescription(index int) string
	ArgTag(index int, tag string) string
	ArgType(index int) reflect.Type
	String() string
}

type ArgsImpl interface {
	Init(outerArgs Args) error
	StringArgsFunc(commandFunc interface{}, resultsHandlers []ResultsHandler) (StringArgsFunc, error)
	StringMapArgsFunc(commandFunc interface{}, resultsHandlers []ResultsHandler) (StringMapArgsFunc, error)
	StringArgsResultValuesFunc(commandFunc interface{}) (StringArgsResultValuesFunc, error)
	StringMapArgsResultValuesFunc(commandFunc interface{}) (StringMapArgsResultValuesFunc, error)
}

func GetStringArgsFunc(commandFunc interface{}, args Args, resultsHandlers ...ResultsHandler) (StringArgsFunc, error) {
	// Note: here happens something unexpected!
	// args implements the Args interface with ArgsDef.
	// This looks like a virtual method call, but of course it is not.
	// The first args is interpreted as (*ArgsDef) to do the method call.
	// We can't use that to get the type that embedds ArgsDef,
	// because ArgsDef knows nothing about the outer embedding type.
	// But args, the first argument to the method, has all the type information,
	// because here the complete outer embedding struct is passed.
	argsImpl := args.(ArgsImpl)
	err := argsImpl.Init(args)
	if err != nil {
		return nil, err
	}
	return argsImpl.StringArgsFunc(commandFunc, resultsHandlers)
}

func MustGetStringArgsFunc(commandFunc interface{}, args Args, resultsHandlers ...ResultsHandler) StringArgsFunc {
	f, err := GetStringArgsFunc(commandFunc, args, resultsHandlers...)
	if err != nil {
		panic(err)
	}
	return f
}

func GetStringMapArgsFunc(commandFunc interface{}, args Args, resultsHandlers ...ResultsHandler) (StringMapArgsFunc, error) {
	// Note: here happens something unexpected!
	// args implements the Args interface with ArgsDef.
	// This looks like a virtual method call, but of course it is not.
	// The first args is interpreted as (*ArgsDef) to do the method call.
	// We can't use that to get the type that embedds ArgsDef,
	// because ArgsDef knows nothing about the outer embedding type.
	// But args, the first argument to the method, has all the type information,
	// because here the complete outer embedding struct is passed.
	argsImpl := args.(ArgsImpl)
	err := argsImpl.Init(args)
	if err != nil {
		return nil, err
	}
	return argsImpl.StringMapArgsFunc(commandFunc, resultsHandlers)
}

func MustGetStringMapArgsFunc(commandFunc interface{}, args Args, resultsHandlers ...ResultsHandler) StringMapArgsFunc {
	f, err := GetStringMapArgsFunc(commandFunc, args, resultsHandlers...)
	if err != nil {
		panic(err)
	}
	return f
}

func GetStringArgsResultValuesFunc(commandFunc interface{}, args Args) (StringArgsResultValuesFunc, error) {
	// Note: here happens something unexpected!
	// args implements the Args interface with ArgsDef.
	// This looks like a virtual method call, but of course it is not.
	// The first args is interpreted as (*ArgsDef) to do the method call.
	// We can't use that to get the type that embedds ArgsDef,
	// because ArgsDef knows nothing about the outer embedding type.
	// But args, the first argument to the method, has all the type information,
	// because here the complete outer embedding struct is passed.
	argsImpl := args.(ArgsImpl)
	err := argsImpl.Init(args)
	if err != nil {
		return nil, err
	}
	return argsImpl.StringArgsResultValuesFunc(commandFunc)
}

func MustGetStringArgsResultValuesFunc(commandFunc interface{}, args Args) StringArgsResultValuesFunc {
	f, err := GetStringArgsResultValuesFunc(commandFunc, args)
	if err != nil {
		panic(err)
	}
	return f
}

func GetStringMapArgsResultValuesFunc(commandFunc interface{}, args Args) (StringMapArgsResultValuesFunc, error) {
	// Note: here happens something unexpected!
	// args implements the Args interface with ArgsDef.
	// This looks like a virtual method call, but of course it is not.
	// The first args is interpreted as (*ArgsDef) to do the method call.
	// We can't use that to get the type that embedds ArgsDef,
	// because ArgsDef knows nothing about the outer embedding type.
	// But args, the first argument to the method, has all the type information,
	// because here the complete outer embedding struct is passed.
	argsImpl := args.(ArgsImpl)
	err := argsImpl.Init(args)
	if err != nil {
		return nil, err
	}
	return argsImpl.StringMapArgsResultValuesFunc(commandFunc)
}

func MustGetStringMapArgsResultValuesFunc(commandFunc interface{}, args Args) StringMapArgsResultValuesFunc {
	f, err := GetStringMapArgsResultValuesFunc(commandFunc, args)
	if err != nil {
		panic(err)
	}
	return f
}

var WithoutArgs ArgsDef

type ArgsDef struct {
	outerArgs       Args
	outerStructType reflect.Type
	argStructFields []reflection.StructFieldName
	initialized     bool
}

func (def *ArgsDef) NumArgs() int {
	return len(def.argStructFields)
}

func (def *ArgsDef) ArgName(index int) string {
	return def.argStructFields[index].Name
}

func (def *ArgsDef) ArgDescription(index int) string {
	return def.ArgTag(index, ArgDescriptionTag)
}

func (def *ArgsDef) ArgTag(index int, tag string) string {
	return def.argStructFields[index].Field.Tag.Get(tag)
}

func (def *ArgsDef) ArgType(index int) reflect.Type {
	return def.argStructFields[index].Field.Type
}

func (def *ArgsDef) String() string {
	if !def.initialized {
		return "ArgsDef not initialized"
	}
	var buf bytes.Buffer
	for _, f := range def.argStructFields {
		if buf.Len() > 0 {
			buf.WriteByte(' ')
		}
		fmt.Fprintf(&buf, "<%s:%s>", f.Name, reflection.DerefType(f.Field.Type))
	}
	return buf.String()
}

func (def *ArgsDef) Init(outerArgs Args) error {
	if def.initialized {
		return nil
	}
	def.outerStructType = reflection.DerefType(reflect.TypeOf(outerArgs))
	if def.outerStructType.Kind() != reflect.Struct {
		return errors.Errorf("ArgsDef must be contained in a struct, but outer type is %s", def.outerStructType)
	}
	def.argStructFields = reflection.FlatExportedStructFieldNames(def.outerStructType, ArgNameTag)
	def.initialized = true
	return nil
}

func (def *ArgsDef) checkFunctionSignature(commandFunc interface{}) (commandFuncVal reflect.Value, numArgs, errorIndex int, err error) {
	commandFuncVal = reflect.ValueOf(commandFunc)
	commandFuncType := commandFuncVal.Type()
	if commandFuncType.Kind() != reflect.Func {
		return reflect.Value{}, -1, -1, errors.Errorf("expected a function or method, but got %s", commandFuncType)
	}

	numResults := commandFuncType.NumOut()
	if numResults > 0 && commandFuncType.Out(numResults-1) == reflection.TypeOfError {
		errorIndex = numResults - 1
	} else {
		errorIndex = -1
	}

	numArgs = len(def.argStructFields)
	if numArgs != commandFuncType.NumIn() {
		return reflect.Value{}, -1, -1, errors.Errorf("number of fields in command.Args struct (%d) does not match number of function arguments (%d)", numArgs, commandFuncType.NumIn())
	}
	for i := range def.argStructFields {
		if def.argStructFields[i].Field.Type != commandFuncType.In(i) {
			return reflect.Value{}, -1, -1, errors.Errorf(
				"type of command.Args struct field '%s' is %s, which does not match function argument %d type %s",
				def.argStructFields[i].Field.Name,
				def.argStructFields[i].Field.Type,
				i,
				commandFuncType.In(i),
			)
		}
	}

	return commandFuncVal, numArgs, errorIndex, nil
}

func (def *ArgsDef) getStringArgsVals(numArgs int, args []string) ([]reflect.Value, error) {
	// Allocate a new args struct because we need addressable
	// variables of struct field types to hold arg values.
	// Instead of new individual variable use fields of args struct.
	argsStruct := reflect.New(def.outerStructType).Elem()
	argVals := make([]reflect.Value, numArgs)
	numStringArgs := len(args)
	for i := range argVals {
		argVals[i] = argsStruct.FieldByIndex(def.argStructFields[i].Field.Index)
		if i >= numStringArgs {
			continue
		}
		err := assignString(argVals[i], args[i])
		if err != nil {
			return nil, err
		}
	}
	return argVals, nil
}

func (def *ArgsDef) getStringMapArgsVals(numArgs int, args map[string]string) ([]reflect.Value, error) {
	// Allocate a new args struct because we need addressable
	// variables of struct field types to hold arg values.
	// Instead of new individual variable use fields of args struct.
	argsStruct := reflect.New(def.outerStructType).Elem()
	argVals := make([]reflect.Value, numArgs)
	for i := range argVals {
		argVals[i] = argsStruct.FieldByIndex(def.argStructFields[i].Field.Index)
		name := def.argStructFields[i].Name
		stringArg, hasArg := args[name]
		if !hasArg {
			continue
		}
		err := assignString(argVals[i], stringArg)
		if err != nil {
			return nil, err
		}
	}
	return argVals, nil
}

func (def *ArgsDef) StringArgsFunc(commandFunc interface{}, resultsHandlers []ResultsHandler) (StringArgsFunc, error) {
	commandFuncVal, numArgs, errorIndex, err := def.checkFunctionSignature(commandFunc)
	if err != nil {
		return nil, err
	}

	return func(args ...string) error {
		argVals, err := def.getStringArgsVals(numArgs, args)
		if err != nil {
			return err
		}

		resultVals := commandFuncVal.Call(argVals)

		var resultErr error
		if errorIndex != -1 {
			resultErr, _ = resultVals[errorIndex].Interface().(error)
			resultVals = resultVals[:errorIndex]
		}
		for _, resultsHandler := range resultsHandlers {
			err = resultsHandler.HandleResults(def.outerArgs, argVals, resultVals, resultErr)
			if err != nil && err != resultErr {
				return err
			}
		}

		return nil
	}, nil
}

func (def *ArgsDef) StringMapArgsFunc(commandFunc interface{}, resultsHandlers []ResultsHandler) (StringMapArgsFunc, error) {
	commandFuncVal, numArgs, errorIndex, err := def.checkFunctionSignature(commandFunc)
	if err != nil {
		return nil, err
	}

	return func(args map[string]string) (err error) {
		argVals, err := def.getStringMapArgsVals(numArgs, args)
		if err != nil {
			return err
		}

		resultVals := commandFuncVal.Call(argVals)

		var resultErr error
		if errorIndex != -1 {
			resultErr, _ = resultVals[errorIndex].Interface().(error)
			resultVals = resultVals[:errorIndex]
		}
		for _, resultsHandler := range resultsHandlers {
			err = resultsHandler.HandleResults(def.outerArgs, argVals, resultVals, resultErr)
			if err != nil && err != resultErr {
				return err
			}
		}

		return nil
	}, nil
}

func (def *ArgsDef) StringArgsResultValuesFunc(commandFunc interface{}) (StringArgsResultValuesFunc, error) {
	commandFuncVal, numArgs, errorIndex, err := def.checkFunctionSignature(commandFunc)
	if err != nil {
		return nil, err
	}

	return func(args []string) ([]reflect.Value, error) {
		argVals, err := def.getStringArgsVals(numArgs, args)
		if err != nil {
			return nil, err
		}

		resultVals := commandFuncVal.Call(argVals)

		var resultErr error
		if errorIndex != -1 {
			resultErr, _ = resultVals[errorIndex].Interface().(error)
			resultVals = resultVals[:errorIndex]
		}
		return resultVals, resultErr
	}, nil
}

func (def *ArgsDef) StringMapArgsResultValuesFunc(commandFunc interface{}) (StringMapArgsResultValuesFunc, error) {
	commandFuncVal, numArgs, errorIndex, err := def.checkFunctionSignature(commandFunc)
	if err != nil {
		return nil, err
	}

	return func(args map[string]string) ([]reflect.Value, error) {
		argVals, err := def.getStringMapArgsVals(numArgs, args)
		if err != nil {
			return nil, err
		}

		resultVals := commandFuncVal.Call(argVals)

		var resultErr error
		if errorIndex != -1 {
			resultErr, _ = resultVals[errorIndex].Interface().(error)
			resultVals = resultVals[:errorIndex]
		}
		return resultVals, resultErr
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2017 Xiaomi, Inc.
//
// 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 expression

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	h "github.com/open-falcon/falcon-plus/modules/api/app/helper"
	f "github.com/open-falcon/falcon-plus/modules/api/app/model/falcon_portal"
)

func GetExpressionList(c *gin.Context) {
	var (
		limit int
		page  int
		err   error
	)
	pageTmp := c.DefaultQuery("page", "")
	limitTmp := c.DefaultQuery("limit", "")
	page, limit, err = h.PageParser(pageTmp, limitTmp)
	if err != nil {
		h.JSONR(c, badstatus, err.Error())
		return
	}
	var dt *gorm.DB
	expressions := []f.Expression{}
	if limit != -1 && page != -1 {
		dt = db.Falcon.Raw(fmt.Sprintf("SELECT * from expression limit %d,%d", page, limit)).Scan(&expressions)
	} else {
		dt = db.Falcon.Find(&expressions)
	}
	if dt.Error != nil {
		h.JSONR(c, badstatus, dt.Error)
		return
	}
	h.JSONR(c, expressions)
	return
}

func GetExpression(c *gin.Context) {
	eidtmp := c.Params.ByName("eid")
	if eidtmp == "" {
		h.JSONR(c, badstatus, "eid is missing")
		return
	}
	eid, err := strconv.Atoi(eidtmp)
	if err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	expression := f.Expression{ID: int64(eid)}
	if dt := db.Falcon.Find(&expression); dt.Error != nil {
		h.JSONR(c, badstatus, dt.Error)
		return
	}
	action := f.Action{ID: expression.ActionId}
	if dt := db.Falcon.Find(&action); dt.Error != nil {
		h.JSONR(c, badstatus, fmt.Sprintf("find action got error: %v", dt.Error.Error()))
		return
	}
	h.JSONR(c, map[string]interface{}{
		"expression": expression,
		"action":     action,
	})
	return
}

type APICreateExrpessionInput struct {
	Expression string    `json:"expression" binding:"required"`
	Func       string    `json:"func" binding:"required"`
	Op         string    `json:"op" binding:"required"`
	RightValue string    `json:"right_value" binding:"required"`
	MaxStep    int       `json:"max_step" binding:"required"`
	Priority   int       `json:"priority" binding:"required"`
	Note       string    `json:"note" binding:"exists"`
	Pause      int       `json:"pause" binding:"exists"`
	Action     ActionTmp `json:"action" binding:"required"`
	// ActionId   string `json:"action_id" binding:"exists"`
}

type ActionTmp struct {
	UIC                []string `json:"uic" binding:"required"`
	URL                string   `json:"url" binding:"exists"`
	Callback           int      `json:"callback" binding:"exists"`
	BeforeCallbackSMS  int      `json:"before_callback_sms" binding:"exists"`
	AfterCallbackSMS   int      `json:"after_callback_sms" binding:"exists"`
	BeforeCallbackMail int      `json:"before_callback_mail" binding:"exists"`
	AfterCallbackMail  int      `json:"after_callback_mail" binding:"exists"`
}

func (this APICreateExrpessionInput) CheckFormat() (err error) {
	validOp := regexp.MustCompile(`^(>|=|<|!)(=)?$`)
	validRightValue := regexp.MustCompile(`^\d+$`)
	switch {
	case !validOp.MatchString(this.Op):
		err = errors.New("op's formating is not vaild")
	case !validRightValue.MatchString(this.RightValue):
		err = errors.New("right_value's formating is not vaild")
	}
	return
}

func CreateExrpession(c *gin.Context) {
	var inputs APICreateExrpessionInput
	if err := c.Bind(&inputs); err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	if err := inputs.CheckFormat(); err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	user, _ := h.GetUser(c)
	tx := db.Falcon.Begin()
	action := f.Action{
		UIC:                strings.Join(inputs.Action.UIC, ","),
		URL:                inputs.Action.URL,
		Callback:           inputs.Action.Callback,
		BeforeCallbackSMS:  inputs.Action.BeforeCallbackSMS,
		BeforeCallbackMail: inputs.Action.BeforeCallbackMail,
		AfterCallbackSMS:   inputs.Action.AfterCallbackSMS,
		AfterCallbackMail:  inputs.Action.AfterCallbackMail,
	}
	if dt := tx.Save(&action); dt.Error != nil {
		h.JSONR(c, expecstatus, dt.Error)
		tx.Rollback()
		return
	}
	expression := f.Expression{
		Expression: inputs.Expression,
		Func:       inputs.Func,
		Op:         inputs.Op,
		RightValue: inputs.RightValue,
		MaxStep:    inputs.MaxStep,
		Priority:   inputs.Priority,
		Note:       inputs.Note,
		Pause:      inputs.Pause,
		CreateUser: user.Name,
		ActionId:   action.ID,
	}
	dt := tx.Save(&expression)
	if dt.Error != nil {
		h.JSONR(c, expecstatus, dt.Error)
		tx.Rollback()
		return
	}
	tx.Commit()
	h.JSONR(c, "expression created")
	return
}

type APIUpdateExrpessionInput struct {
	ID         int64      `json:"id"  binding:"required"`
	Expression string     `json:"expression" binding:"required"`
	Func       string     `json:"func" binding:"required"`
	Op         string     `json:"op" binding:"required"`
	RightValue string     `json:"right_value" binding:"required"`
	MaxStep    int        `json:"max_step" binding:"required"`
	Priority   int        `json:"priority" binding:"required"`
	Note       string     `json:"note" binding:"exists"`
	Pause      int        `json:"pause" binding:"exists"`
	Action     ActionTmpU `json:"action" binding:"required"`
}

type ActionTmpU struct {
	UIC                []string `json:"uic" binding:"required"`
	URL                string   `json:"url" binding:"exists"`
	Callback           int      `json:"callback" binding:"exists"`
	BeforeCallbackSMS  int      `json:"before_callback_sms" binding:"exists"`
	AfterCallbackSMS   int      `json:"after_callback_sms" binding:"exists"`
	BeforeCallbackMail int      `json:"before_callback_mail" binding:"exists"`
	AfterCallbackMail  int      `json:"after_callback_mail" binding:"exists"`
}

func (this APIUpdateExrpessionInput) CheckFormat() (err error) {
	validOp := regexp.MustCompile(`^(>|=|<|!)(=)?$`)
	validRightValue := regexp.MustCompile(`^\d+$`)
	switch {
	case !validOp.MatchString(this.Op):
		err = errors.New("op's formating is not vaild")
	case !validRightValue.MatchString(this.RightValue):
		err = errors.New("right_value's formating is not vaild")
	}
	return
}

func UpdateExrpession(c *gin.Context) {
	var inputs APIUpdateExrpessionInput
	if err := c.Bind(&inputs); err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	if err := inputs.CheckFormat(); err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	tx := db.Falcon.Begin()
	user, _ := h.GetUser(c)
	expression := f.Expression{ID: inputs.ID}
	if dt := tx.Find(&expression); dt.Error != nil {
		h.JSONR(c, expecstatus, fmt.Sprintf(
			"find expression got error:%v", dt.Error.Error()))
		tx.Rollback()
		return
	}
	if !user.IsAdmin() {
		if expression.CreateUser != user.Name {
			h.JSONR(c, badstatus, "You don't have permission!")
			tx.Rollback()
			return
		}
	}
	uexpression := map[string]interface{}{
		"ID":         expression.ID,
		"Expression": inputs.Expression,
		"Func":       inputs.Func,
		"Op":         inputs.Op,
		"RightValue": inputs.RightValue,
		"MaxStep":    inputs.MaxStep,
		"Priority":   inputs.Priority,
		"Note":       inputs.Note,
		"Pause":      inputs.Pause,
	}
	dt := tx.Model(&expression).Where("id = ?", expression.ID).Update(uexpression).Find(&expression)
	if dt.Error != nil {
		h.JSONR(c, expecstatus, fmt.Sprintf(
			"update expression got error: %v", dt.Error))
		tx.Rollback()
		return
	}
	actionTmp := f.Action{ID: expression.ActionId}
	uaction := map[string]interface{}{
		"ID":                 actionTmp.ID,
		"UIC":                strings.Join(inputs.Action.UIC, ","),
		"URL":                inputs.Action.URL,
		"Callback":           inputs.Action.Callback,
		"BeforeCallbackSMS":  inputs.Action.BeforeCallbackSMS,
		"BeforeCallbackMail": inputs.Action.BeforeCallbackMail,
		"AfterCallbackSMS":   inputs.Action.AfterCallbackSMS,
		"AfterCallbackMail":  inputs.Action.AfterCallbackMail,
	}
	if dt = tx.Find(&actionTmp, expression.ActionId); dt.Error != nil {
		h.JSONR(c, expecstatus, fmt.Sprintf(
			"find action got error: %v", dt.Error))
		tx.Rollback()
		return
	}
	dt = tx.Model(&actionTmp).Where("id = ?", actionTmp.ID).Update(uaction)
	if dt.Error != nil {
		h.JSONR(c, expecstatus, dt.Error)
		tx.Rollback()
		return
	}
	tx.Commit()
	h.JSONR(c, fmt.Sprintf("expression:%v has been updated", inputs.ID))
	return
}

func DeleteExpression(c *gin.Context) {
	eidtmp := c.Params.ByName("eid")
	if eidtmp == "" {
		h.JSONR(c, badstatus, "eid is missing")
		return
	}
	eid, err := strconv.Atoi(eidtmp)
	if err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	tx := db.Falcon.Begin()
	user, _ := h.GetUser(c)
	expression := f.Expression{ID: int64(eid)}
	if !user.IsAdmin() {
		tx.Find(&expression)
		if expression.CreateUser != user.Name {
			h.JSONR(c, badstatus, "You don't have permission!")
			tx.Rollback()
			return
		}
	}
	dt := tx.Table("action").Where("id = ?", expression.ActionId).Delete(&f.Action{})
	if dt.Error != nil {
		h.JSONR(c, badstatus, dt.Error)
		tx.Rollback()
		return
	}
	if dt := tx.Delete(&expression); dt.Error != nil {
		h.JSONR(c, badstatus, dt.Error)
		tx.Rollback()
		return
	}
	tx.Commit()
	h.JSONR(c, fmt.Sprintf("expression:%d has been deleted", eid))
	return
}
<commit_msg>[api] fix right value regex pattern of expression api https://github.com/open-falcon/falcon-plus/issues/331<commit_after>// Copyright 2017 Xiaomi, Inc.
//
// 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 expression

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	h "github.com/open-falcon/falcon-plus/modules/api/app/helper"
	f "github.com/open-falcon/falcon-plus/modules/api/app/model/falcon_portal"
)

func GetExpressionList(c *gin.Context) {
	var (
		limit int
		page  int
		err   error
	)
	pageTmp := c.DefaultQuery("page", "")
	limitTmp := c.DefaultQuery("limit", "")
	page, limit, err = h.PageParser(pageTmp, limitTmp)
	if err != nil {
		h.JSONR(c, badstatus, err.Error())
		return
	}
	var dt *gorm.DB
	expressions := []f.Expression{}
	if limit != -1 && page != -1 {
		dt = db.Falcon.Raw(fmt.Sprintf("SELECT * from expression limit %d,%d", page, limit)).Scan(&expressions)
	} else {
		dt = db.Falcon.Find(&expressions)
	}
	if dt.Error != nil {
		h.JSONR(c, badstatus, dt.Error)
		return
	}
	h.JSONR(c, expressions)
	return
}

func GetExpression(c *gin.Context) {
	eidtmp := c.Params.ByName("eid")
	if eidtmp == "" {
		h.JSONR(c, badstatus, "eid is missing")
		return
	}
	eid, err := strconv.Atoi(eidtmp)
	if err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	expression := f.Expression{ID: int64(eid)}
	if dt := db.Falcon.Find(&expression); dt.Error != nil {
		h.JSONR(c, badstatus, dt.Error)
		return
	}
	action := f.Action{ID: expression.ActionId}
	if dt := db.Falcon.Find(&action); dt.Error != nil {
		h.JSONR(c, badstatus, fmt.Sprintf("find action got error: %v", dt.Error.Error()))
		return
	}
	h.JSONR(c, map[string]interface{}{
		"expression": expression,
		"action":     action,
	})
	return
}

type APICreateExrpessionInput struct {
	Expression string    `json:"expression" binding:"required"`
	Func       string    `json:"func" binding:"required"`
	Op         string    `json:"op" binding:"required"`
	RightValue string    `json:"right_value" binding:"required"`
	MaxStep    int       `json:"max_step" binding:"required"`
	Priority   int       `json:"priority" binding:"required"`
	Note       string    `json:"note" binding:"exists"`
	Pause      int       `json:"pause" binding:"exists"`
	Action     ActionTmp `json:"action" binding:"required"`
	// ActionId   string `json:"action_id" binding:"exists"`
}

type ActionTmp struct {
	UIC                []string `json:"uic" binding:"required"`
	URL                string   `json:"url" binding:"exists"`
	Callback           int      `json:"callback" binding:"exists"`
	BeforeCallbackSMS  int      `json:"before_callback_sms" binding:"exists"`
	AfterCallbackSMS   int      `json:"after_callback_sms" binding:"exists"`
	BeforeCallbackMail int      `json:"before_callback_mail" binding:"exists"`
	AfterCallbackMail  int      `json:"after_callback_mail" binding:"exists"`
}

func (this APICreateExrpessionInput) CheckFormat() (err error) {
	validOp := regexp.MustCompile(`^(>|=|<|!)(=)?$`)
	validRightValue := regexp.MustCompile(`^\-?\d+(\.\d+)?$`)
	switch {
	case !validOp.MatchString(this.Op):
		err = errors.New("op's formating is not vaild")
	case !validRightValue.MatchString(this.RightValue):
		err = errors.New("right_value's formating is not vaild")
	}
	return
}

func CreateExrpession(c *gin.Context) {
	var inputs APICreateExrpessionInput
	if err := c.Bind(&inputs); err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	if err := inputs.CheckFormat(); err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	user, _ := h.GetUser(c)
	tx := db.Falcon.Begin()
	action := f.Action{
		UIC:                strings.Join(inputs.Action.UIC, ","),
		URL:                inputs.Action.URL,
		Callback:           inputs.Action.Callback,
		BeforeCallbackSMS:  inputs.Action.BeforeCallbackSMS,
		BeforeCallbackMail: inputs.Action.BeforeCallbackMail,
		AfterCallbackSMS:   inputs.Action.AfterCallbackSMS,
		AfterCallbackMail:  inputs.Action.AfterCallbackMail,
	}
	if dt := tx.Save(&action); dt.Error != nil {
		h.JSONR(c, expecstatus, dt.Error)
		tx.Rollback()
		return
	}
	expression := f.Expression{
		Expression: inputs.Expression,
		Func:       inputs.Func,
		Op:         inputs.Op,
		RightValue: inputs.RightValue,
		MaxStep:    inputs.MaxStep,
		Priority:   inputs.Priority,
		Note:       inputs.Note,
		Pause:      inputs.Pause,
		CreateUser: user.Name,
		ActionId:   action.ID,
	}
	dt := tx.Save(&expression)
	if dt.Error != nil {
		h.JSONR(c, expecstatus, dt.Error)
		tx.Rollback()
		return
	}
	tx.Commit()
	h.JSONR(c, "expression created")
	return
}

type APIUpdateExrpessionInput struct {
	ID         int64      `json:"id"  binding:"required"`
	Expression string     `json:"expression" binding:"required"`
	Func       string     `json:"func" binding:"required"`
	Op         string     `json:"op" binding:"required"`
	RightValue string     `json:"right_value" binding:"required"`
	MaxStep    int        `json:"max_step" binding:"required"`
	Priority   int        `json:"priority" binding:"required"`
	Note       string     `json:"note" binding:"exists"`
	Pause      int        `json:"pause" binding:"exists"`
	Action     ActionTmpU `json:"action" binding:"required"`
}

type ActionTmpU struct {
	UIC                []string `json:"uic" binding:"required"`
	URL                string   `json:"url" binding:"exists"`
	Callback           int      `json:"callback" binding:"exists"`
	BeforeCallbackSMS  int      `json:"before_callback_sms" binding:"exists"`
	AfterCallbackSMS   int      `json:"after_callback_sms" binding:"exists"`
	BeforeCallbackMail int      `json:"before_callback_mail" binding:"exists"`
	AfterCallbackMail  int      `json:"after_callback_mail" binding:"exists"`
}

func (this APIUpdateExrpessionInput) CheckFormat() (err error) {
	validOp := regexp.MustCompile(`^(>|=|<|!)(=)?$`)
	validRightValue := regexp.MustCompile(`^\d+$`)
	switch {
	case !validOp.MatchString(this.Op):
		err = errors.New("op's formating is not vaild")
	case !validRightValue.MatchString(this.RightValue):
		err = errors.New("right_value's formating is not vaild")
	}
	return
}

func UpdateExrpession(c *gin.Context) {
	var inputs APIUpdateExrpessionInput
	if err := c.Bind(&inputs); err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	if err := inputs.CheckFormat(); err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	tx := db.Falcon.Begin()
	user, _ := h.GetUser(c)
	expression := f.Expression{ID: inputs.ID}
	if dt := tx.Find(&expression); dt.Error != nil {
		h.JSONR(c, expecstatus, fmt.Sprintf(
			"find expression got error:%v", dt.Error.Error()))
		tx.Rollback()
		return
	}
	if !user.IsAdmin() {
		if expression.CreateUser != user.Name {
			h.JSONR(c, badstatus, "You don't have permission!")
			tx.Rollback()
			return
		}
	}
	uexpression := map[string]interface{}{
		"ID":         expression.ID,
		"Expression": inputs.Expression,
		"Func":       inputs.Func,
		"Op":         inputs.Op,
		"RightValue": inputs.RightValue,
		"MaxStep":    inputs.MaxStep,
		"Priority":   inputs.Priority,
		"Note":       inputs.Note,
		"Pause":      inputs.Pause,
	}
	dt := tx.Model(&expression).Where("id = ?", expression.ID).Update(uexpression).Find(&expression)
	if dt.Error != nil {
		h.JSONR(c, expecstatus, fmt.Sprintf(
			"update expression got error: %v", dt.Error))
		tx.Rollback()
		return
	}
	actionTmp := f.Action{ID: expression.ActionId}
	uaction := map[string]interface{}{
		"ID":                 actionTmp.ID,
		"UIC":                strings.Join(inputs.Action.UIC, ","),
		"URL":                inputs.Action.URL,
		"Callback":           inputs.Action.Callback,
		"BeforeCallbackSMS":  inputs.Action.BeforeCallbackSMS,
		"BeforeCallbackMail": inputs.Action.BeforeCallbackMail,
		"AfterCallbackSMS":   inputs.Action.AfterCallbackSMS,
		"AfterCallbackMail":  inputs.Action.AfterCallbackMail,
	}
	if dt = tx.Find(&actionTmp, expression.ActionId); dt.Error != nil {
		h.JSONR(c, expecstatus, fmt.Sprintf(
			"find action got error: %v", dt.Error))
		tx.Rollback()
		return
	}
	dt = tx.Model(&actionTmp).Where("id = ?", actionTmp.ID).Update(uaction)
	if dt.Error != nil {
		h.JSONR(c, expecstatus, dt.Error)
		tx.Rollback()
		return
	}
	tx.Commit()
	h.JSONR(c, fmt.Sprintf("expression:%v has been updated", inputs.ID))
	return
}

func DeleteExpression(c *gin.Context) {
	eidtmp := c.Params.ByName("eid")
	if eidtmp == "" {
		h.JSONR(c, badstatus, "eid is missing")
		return
	}
	eid, err := strconv.Atoi(eidtmp)
	if err != nil {
		h.JSONR(c, badstatus, err)
		return
	}
	tx := db.Falcon.Begin()
	user, _ := h.GetUser(c)
	expression := f.Expression{ID: int64(eid)}
	if !user.IsAdmin() {
		tx.Find(&expression)
		if expression.CreateUser != user.Name {
			h.JSONR(c, badstatus, "You don't have permission!")
			tx.Rollback()
			return
		}
	}
	dt := tx.Table("action").Where("id = ?", expression.ActionId).Delete(&f.Action{})
	if dt.Error != nil {
		h.JSONR(c, badstatus, dt.Error)
		tx.Rollback()
		return
	}
	if dt := tx.Delete(&expression); dt.Error != nil {
		h.JSONR(c, badstatus, dt.Error)
		tx.Rollback()
		return
	}
	tx.Commit()
	h.JSONR(c, fmt.Sprintf("expression:%d has been deleted", eid))
	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"github.com/dgrijalva/jwt-go"
	"time"
	"github.com/gorilla/mux"
	"encoding/json"
	"strings"
	"io/ioutil"
	"crypto/rsa"
)

var (
	verifyKey  *rsa.PublicKey
	signKey    *rsa.PrivateKey
)

func loadKeys(subConfig *SubConfig) {

	signBytes, err := ioutil.ReadFile(subConfig.PrivateKey)
	fatal(err)

	signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
	fatal(err)

	verifyBytes, err := ioutil.ReadFile(subConfig.PublicKey)
	fatal(err)

	verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
	fatal(err)
}

func registerAuth(r *mux.Router, subConfig *SubConfig) {
	loadKeys(subConfig)

	r.Handle("/authenticate", GetTokenHandler).Methods("POST")
}

/* Set up a global string for our secret */
const (
	ValidUser = "foo"
	ValidPass = "bar"
)

type User struct{
	Id      string
	Password string
}

func createToken (user User) (string, error) {
	/* Create the token */
	token := jwt.New(jwt.GetSigningMethod("RS256"))

	/* Set token claims */
	claims := make(jwt.MapClaims)
	claims["admin"] = true
	claims["username"] = user.Id
	claims["exp"] = time.Now().Add(time.Hour * 24).Unix()

	/* Sign the token with our secret */
	return token.SignedString(signKey)
}

/* Handlers */
var GetTokenHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
	var user User
	if r.Body == nil {
		http.Error(w, "Please send a request body", 400)
		return
	}
	err := json.NewDecoder(r.Body).Decode(&user)
	if err != nil {
		http.Error(w, err.Error(), 400)
		return
	}

	if user.Id == ValidUser && user.Password == ValidPass {

		tokenString, err := createToken(user)
		/* Finally, write the token to the browser window */
		if err != nil {
			fmt.Println("User or Password is not valid:", tokenString)
			w.WriteHeader(http.StatusUnauthorized)
			w.Write([]byte("Unauthorized"))
		} else {

			w.Write([]byte(tokenString))
		}
	}  else {
		fmt.Println(err)
		fmt.Println("User or Password is not valid:")
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("Unauthorized"))
	}
	w.Header().Set("Access-Control-Allow-Origin", "*")
})

func authMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		reqToken := r.Header.Get("Authorization")
		splitToken := strings.Split(reqToken, "Bearer")
		reqToken = splitToken[1]

		token, err := jwt.Parse(reqToken, func(token *jwt.Token) (interface{}, error) {
			// Don't forget to validate the alg is what you expect:
			return verifyKey, nil
		})

		if err == nil && token.Valid {
			next.ServeHTTP(w, r)
		} else {
			fmt.Println(err)
			fmt.Println("Token is not valid:")
			w.WriteHeader(http.StatusUnauthorized)
			w.Write([]byte("Unauthorized"))
		}
	})
}

<commit_msg>add other headers<commit_after>package main

import (
	"fmt"
	"net/http"
	"github.com/dgrijalva/jwt-go"
	"time"
	"github.com/gorilla/mux"
	"encoding/json"
	"strings"
	"io/ioutil"
	"crypto/rsa"
)

var (
	verifyKey  *rsa.PublicKey
	signKey    *rsa.PrivateKey
)

func loadKeys(subConfig *SubConfig) {

	signBytes, err := ioutil.ReadFile(subConfig.PrivateKey)
	fatal(err)

	signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
	fatal(err)

	verifyBytes, err := ioutil.ReadFile(subConfig.PublicKey)
	fatal(err)

	verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
	fatal(err)
}

func registerAuth(r *mux.Router, subConfig *SubConfig) {
	loadKeys(subConfig)

	r.Handle("/authenticate", GetTokenHandler).Methods("POST")
}

/* Set up a global string for our secret */
const (
	ValidUser = "foo"
	ValidPass = "bar"
)

type User struct{
	Id      string
	Password string
}

func createToken (user User) (string, error) {
	/* Create the token */
	token := jwt.New(jwt.GetSigningMethod("RS256"))

	/* Set token claims */
	claims := make(jwt.MapClaims)
	claims["admin"] = true
	claims["username"] = user.Id
	claims["exp"] = time.Now().Add(time.Hour * 24).Unix()

	/* Sign the token with our secret */
	return token.SignedString(signKey)
}

/* Handlers */
var GetTokenHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
	var user User
	if r.Body == nil {
		http.Error(w, "Please send a request body", 400)
		return
	}
	err := json.NewDecoder(r.Body).Decode(&user)
	if err != nil {
		http.Error(w, err.Error(), 400)
		return
	}

	if user.Id == ValidUser && user.Password == ValidPass {

		tokenString, err := createToken(user)
		/* Finally, write the token to the browser window */
		if err != nil {
			fmt.Println("User or Password is not valid:", tokenString)
			w.WriteHeader(http.StatusUnauthorized)
			w.Write([]byte("Unauthorized"))
		} else {

			w.Write([]byte(tokenString))
		}
	}  else {
		fmt.Println(err)
		fmt.Println("User or Password is not valid:")
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("Unauthorized"))
	}
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token")
	w.Header().Set("Access-Control-Allow-Credentials", "true")
})

func authMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		reqToken := r.Header.Get("Authorization")
		splitToken := strings.Split(reqToken, "Bearer")
		reqToken = splitToken[1]

		token, err := jwt.Parse(reqToken, func(token *jwt.Token) (interface{}, error) {
			// Don't forget to validate the alg is what you expect:
			return verifyKey, nil
		})

		if err == nil && token.Valid {
			next.ServeHTTP(w, r)
		} else {
			fmt.Println(err)
			fmt.Println("Token is not valid:")
			w.WriteHeader(http.StatusUnauthorized)
			w.Write([]byte("Unauthorized"))
		}
	})
}

<|endoftext|>