<commit_before>package humanize

import (
	"fmt"
	"go/ast"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
)

// pkg is list of files
type Package struct {
	Files []*File
	Path  string
	Name  string
}

var (
	packageCache = make(map[string]*Package)
	lock         = sync.RWMutex{}
)

func setCache(path string, p *Package) {
	lock.Lock()
	defer lock.Unlock()

	packageCache[path] = p
}

func getCache(path string) *Package {
	lock.RLock()
	defer lock.RUnlock()

	return packageCache[path]
}

// FindType return a base type interface base on the string name of the type
func (p Package) FindType(t string) (*TypeName, error) {
	for i := range p.Files {
		for j := range p.Files[i].Types {
			if p.Files[i].Types[j].Name == t {
				return p.Files[i].Types[j], nil
			}
		}
	}

	return nil, fmt.Errorf("type with name %s not found", t)
}

// FindVariable try to find a package level variable
func (p Package) FindVariable(t string) (*Variable, error) {
	for i := range p.Files {
		for j := range p.Files[i].Variables {
			if p.Files[i].Variables[j].Name == t {
				return p.Files[i].Variables[j], nil
			}
		}
	}

	return nil, fmt.Errorf("var with name %s not found", t)
}

// FindConstant try to find a package level variable
func (p Package) FindConstant(t string) (*Constant, error) {
	for i := range p.Files {
		for j := range p.Files[i].Constants {
			if p.Files[i].Constants[j].Name == t {
				return p.Files[i].Constants[j], nil
			}
		}
	}

	return nil, fmt.Errorf("const with name %s not found", t)
}

// FindFunction try to find a package level variable
func (p Package) FindFunction(t string) (*Function, error) {
	for i := range p.Files {
		for j := range p.Files[i].Functions {
			if p.Files[i].Functions[j].Name == t {
				return p.Files[i].Functions[j], nil
			}
		}
	}
	return nil, fmt.Errorf("func with name %s not found", t)
}

// FindImport try to find an import by its full import path
func (p Package) FindImport(t string) (*Import, error) {
	if t == "" || t == "_" || t == "." {
		return nil, fmt.Errorf("import with path _/. or empty is invalid")
	}
	for i := range p.Files {
		for j := range p.Files[i].Imports {
			if p.Files[i].Imports[j].Name == t || p.Files[i].Imports[j].Path == t {
				return p.Files[i].Imports[j], nil
			}
		}
	}

	return nil, fmt.Errorf("import with name or path %s not found", t)
}

func translateToFullPath(path string) (string, error) {
	root := runtime.GOROOT()
	gopath := strings.Split(os.Getenv("GOPATH"), ":")

	test := filepath.Join(root, "src", path)
	r, err := os.Stat(test)
	if err != nil {
		for i := range gopath {
			test = filepath.Join(gopath[i], "src", path)
			r, err = os.Stat(test)
			if err == nil {
				break
			}
		}
		if err != nil {
			return "", fmt.Errorf("%s is not found in GOROOT or GOPATH", path)
		}
	}

	if !r.IsDir() {
		return "", fmt.Errorf("%s is found in %s but its not a directory", path, r.Name())
	}

	return test, nil
}

func lateBind(p *Package) (res error) {
	for f := range p.Files {
		// Try to find variable with null type and change them to real type
	thebigLoop:
		for v := range p.Files[f].Variables {
			if p.Files[f].Variables[v].caller != nil {
				switch c := p.Files[f].Variables[v].caller.Fun.(type) {
				case *ast.Ident:
					name := nameFromIdent(c)
					// TODO : list all builtin functions?
					if name == "make" || name == "new" {
						p.Files[f].Variables[v].Type = getType(p.Files[f].Variables[v].caller.Args[0], "", p.Files[f], p)
					} else {
						fn, err := p.FindFunction(name)
						if err != nil {
							return err
						}

						if len(fn.Type.Results) <= p.Files[f].Variables[v].indx {
							return fmt.Errorf("%d result is available but want the %d", len(fn.Type.Results), p.Files[f].Variables[v].indx)
						}
						p.Files[f].Variables[v].Type = fn.Type.Results[p.Files[f].Variables[v].indx].Type
					}
				case *ast.SelectorExpr:
					var pkg string
					switch c.X.(type) {
					case *ast.Ident:
						pkg = nameFromIdent(c.X.(*ast.Ident))
					case *ast.CallExpr: // TODO : Don't know why, no time for check
						continue thebigLoop
					}

					typ := nameFromIdent(c.Sel)
					imprt, err := p.FindImport(pkg)
					if err != nil {
						// TODO : package currently is not capable of parsing build tags. so ignore this :/
						continue thebigLoop
					}
					pkgDef, err := ParsePackage(imprt.Path)
					if err != nil {
						return err
					}
					fn, err := pkgDef.FindFunction(typ)
					if err != nil {
						return err
					}

					if len(fn.Type.Results) <= p.Files[f].Variables[v].indx {
						return fmt.Errorf("%d result is available but want the %d", len(fn.Type.Results), p.Files[f].Variables[v].indx)
					}
					foreignTyp := fn.Type.Results[p.Files[f].Variables[v].indx].Type
					star := false
					if sType, ok := foreignTyp.(*StarType); ok {
						foreignTyp = sType.Target
						star = true
					}
					switch ft := foreignTyp.(type) {
					case *IdentType:
						// this is a simple hack. if the type is begin with
						// upper case, then its type on that package, else its a global type
						name := ft.Ident
						c := name[0]
						if c >= 'A' && c <= 'Z' {
							if star {
								foreignTyp = &StarType{
									ft.srcBase,
									foreignTyp,
								}
							}
							p.Files[f].Variables[v].Type = &SelectorType{
								srcBase: srcBase{p, ""}, // TODO : source?
								pkg:     getImport(imprt.Name, p.Files[f]),
								Type:    foreignTyp,
							}
						} else {
							if star {
								foreignTyp = &StarType{
									ft.srcBase,
									foreignTyp,
								}
							}
							p.Files[f].Variables[v].Type = foreignTyp
						}

					default:
						// the type is foreign to that package too
						p.Files[f].Variables[v].Type = ft
					}
				}
			}
		}
	}
	return nil
}

func findMethods(p *Package) error {
	for _, f := range p.Files {
		for _, fn := range f.Functions {
			if fn.Receiver != nil {
				t := fn.Receiver.Type
				var pointer bool
				if t2, ok := t.(*StarType); ok {
					t = t2.Target
					pointer = true
				}
				nt, err := p.FindType(t.GetDefinition())
				if err != nil {
					return err
				}
				if pointer {
					nt.StarMethods = append(nt.StarMethods, fn)
				} else {
					nt.Methods = append(nt.Methods, fn)
				}
			}
		}
	}

	return nil
}

// ParsePackage is here for loading a single package and parse all files in it
func ParsePackage(path string) (*Package, error) {
	if p := getCache(path); p != nil {
		return p, nil
	}
	var p = &Package{}
	p.Path = path
	folder, err := translateToFullPath(path)
	if err != nil {
		return nil, err
	}
	err = filepath.Walk(
		folder,
		func(path string, f os.FileInfo, err error) error {
			if f.IsDir() {
				return nil
			}
			if filepath.Ext(path) != ".go" {
				return nil
			}
			r, err := os.Open(path)
			if err != nil {
				return err
			}
			defer r.Close()

			data, err := ioutil.ReadAll(r)
			if err != nil {
				return err
			}

			fl, err := ParseFile(string(data), p)
			if err != nil {
				return err
			}
			fl.FileName = path
			p.Files = append(p.Files, fl)
			p.Name = fl.PackageName

			return nil
		},
	)
	if err != nil {
		return nil, err
	}
	setCache(path, p)
	err = lateBind(p)
	if err != nil {
		return nil, err
	}

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

func assertNil(e interface{}) {
	if e != nil {
		panic(e)
	}
}
<commit_msg>ignore tests once more<commit_after>package humanize

import (
	"fmt"
	"go/ast"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
)

// pkg is list of files
type Package struct {
	Files []*File
	Path  string
	Name  string
}

var (
	packageCache = make(map[string]*Package)
	lock         = sync.RWMutex{}
)

func setCache(path string, p *Package) {
	lock.Lock()
	defer lock.Unlock()

	packageCache[path] = p
}

func getCache(path string) *Package {
	lock.RLock()
	defer lock.RUnlock()

	return packageCache[path]
}

// FindType return a base type interface base on the string name of the type
func (p Package) FindType(t string) (*TypeName, error) {
	for i := range p.Files {
		for j := range p.Files[i].Types {
			if p.Files[i].Types[j].Name == t {
				return p.Files[i].Types[j], nil
			}
		}
	}

	return nil, fmt.Errorf("type with name %s not found", t)
}

// FindVariable try to find a package level variable
func (p Package) FindVariable(t string) (*Variable, error) {
	for i := range p.Files {
		for j := range p.Files[i].Variables {
			if p.Files[i].Variables[j].Name == t {
				return p.Files[i].Variables[j], nil
			}
		}
	}

	return nil, fmt.Errorf("var with name %s not found", t)
}

// FindConstant try to find a package level variable
func (p Package) FindConstant(t string) (*Constant, error) {
	for i := range p.Files {
		for j := range p.Files[i].Constants {
			if p.Files[i].Constants[j].Name == t {
				return p.Files[i].Constants[j], nil
			}
		}
	}

	return nil, fmt.Errorf("const with name %s not found", t)
}

// FindFunction try to find a package level variable
func (p Package) FindFunction(t string) (*Function, error) {
	for i := range p.Files {
		for j := range p.Files[i].Functions {
			if p.Files[i].Functions[j].Name == t {
				return p.Files[i].Functions[j], nil
			}
		}
	}
	return nil, fmt.Errorf("func with name %s not found", t)
}

// FindImport try to find an import by its full import path
func (p Package) FindImport(t string) (*Import, error) {
	if t == "" || t == "_" || t == "." {
		return nil, fmt.Errorf("import with path _/. or empty is invalid")
	}
	for i := range p.Files {
		for j := range p.Files[i].Imports {
			if p.Files[i].Imports[j].Name == t || p.Files[i].Imports[j].Path == t {
				return p.Files[i].Imports[j], nil
			}
		}
	}

	return nil, fmt.Errorf("import with name or path %s not found", t)
}

func translateToFullPath(path string) (string, error) {
	root := runtime.GOROOT()
	gopath := strings.Split(os.Getenv("GOPATH"), ":")

	test := filepath.Join(root, "src", path)
	r, err := os.Stat(test)
	if err != nil {
		for i := range gopath {
			test = filepath.Join(gopath[i], "src", path)
			r, err = os.Stat(test)
			if err == nil {
				break
			}
		}
		if err != nil {
			return "", fmt.Errorf("%s is not found in GOROOT or GOPATH", path)
		}
	}

	if !r.IsDir() {
		return "", fmt.Errorf("%s is found in %s but its not a directory", path, r.Name())
	}

	return test, nil
}

func lateBind(p *Package) (res error) {
	for f := range p.Files {
		// Try to find variable with null type and change them to real type
	thebigLoop:
		for v := range p.Files[f].Variables {
			if p.Files[f].Variables[v].caller != nil {
				switch c := p.Files[f].Variables[v].caller.Fun.(type) {
				case *ast.Ident:
					name := nameFromIdent(c)
					// TODO : list all builtin functions?
					if name == "make" || name == "new" {
						p.Files[f].Variables[v].Type = getType(p.Files[f].Variables[v].caller.Args[0], "", p.Files[f], p)
					} else {
						fn, err := p.FindFunction(name)
						if err != nil {
							return err
						}

						if len(fn.Type.Results) <= p.Files[f].Variables[v].indx {
							return fmt.Errorf("%d result is available but want the %d", len(fn.Type.Results), p.Files[f].Variables[v].indx)
						}
						p.Files[f].Variables[v].Type = fn.Type.Results[p.Files[f].Variables[v].indx].Type
					}
				case *ast.SelectorExpr:
					var pkg string
					switch c.X.(type) {
					case *ast.Ident:
						pkg = nameFromIdent(c.X.(*ast.Ident))
					case *ast.CallExpr: // TODO : Don't know why, no time for check
						continue thebigLoop
					}

					typ := nameFromIdent(c.Sel)
					imprt, err := p.FindImport(pkg)
					if err != nil {
						// TODO : package currently is not capable of parsing build tags. so ignore this :/
						continue thebigLoop
					}
					pkgDef, err := ParsePackage(imprt.Path)
					if err != nil {
						return err
					}
					fn, err := pkgDef.FindFunction(typ)
					if err != nil {
						return err
					}

					if len(fn.Type.Results) <= p.Files[f].Variables[v].indx {
						return fmt.Errorf("%d result is available but want the %d", len(fn.Type.Results), p.Files[f].Variables[v].indx)
					}
					foreignTyp := fn.Type.Results[p.Files[f].Variables[v].indx].Type
					star := false
					if sType, ok := foreignTyp.(*StarType); ok {
						foreignTyp = sType.Target
						star = true
					}
					switch ft := foreignTyp.(type) {
					case *IdentType:
						// this is a simple hack. if the type is begin with
						// upper case, then its type on that package, else its a global type
						name := ft.Ident
						c := name[0]
						if c >= 'A' && c <= 'Z' {
							if star {
								foreignTyp = &StarType{
									ft.srcBase,
									foreignTyp,
								}
							}
							p.Files[f].Variables[v].Type = &SelectorType{
								srcBase: srcBase{p, ""}, // TODO : source?
								pkg:     getImport(imprt.Name, p.Files[f]),
								Type:    foreignTyp,
							}
						} else {
							if star {
								foreignTyp = &StarType{
									ft.srcBase,
									foreignTyp,
								}
							}
							p.Files[f].Variables[v].Type = foreignTyp
						}

					default:
						// the type is foreign to that package too
						p.Files[f].Variables[v].Type = ft
					}
				}
			}
		}
	}
	return nil
}

func findMethods(p *Package) error {
	for _, f := range p.Files {
		for _, fn := range f.Functions {
			if fn.Receiver != nil {
				t := fn.Receiver.Type
				var pointer bool
				if t2, ok := t.(*StarType); ok {
					t = t2.Target
					pointer = true
				}
				nt, err := p.FindType(t.GetDefinition())
				if err != nil {
					return err
				}
				if pointer {
					nt.StarMethods = append(nt.StarMethods, fn)
				} else {
					nt.Methods = append(nt.Methods, fn)
				}
			}
		}
	}

	return nil
}

// ParsePackage is here for loading a single package and parse all files in it
func ParsePackage(path string) (*Package, error) {
	if p := getCache(path); p != nil {
		return p, nil
	}
	var p = &Package{}
	p.Path = path
	folder, err := translateToFullPath(path)
	if err != nil {
		return nil, err
	}
	err = filepath.Walk(
		folder,
		func(path string, f os.FileInfo, err error) error {
			if f.IsDir() {
				return nil
			}
			if filepath.Ext(path) != ".go" {
				return nil
			}
			// ignore test files (for now?)
			_, filename := filepath.Split(path)
			if len(filename) > 8 && filename[len(filename)-8:] == "_test.go" {
				return nil
			}
			r, err := os.Open(path)
			if err != nil {
				return err
			}
			defer r.Close()

			data, err := ioutil.ReadAll(r)
			if err != nil {
				return err
			}

			fl, err := ParseFile(string(data), p)
			if err != nil {
				return err
			}
			fl.FileName = path
			p.Files = append(p.Files, fl)
			p.Name = fl.PackageName

			return nil
		},
	)
	if err != nil {
		return nil, err
	}
	setCache(path, p)
	err = lateBind(p)
	if err != nil {
		return nil, err
	}

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

func assertNil(e interface{}) {
	if e != nil {
		panic(e)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"

	"github.com/dickeyxxx/gode"
	"github.com/stvp/rollbar"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Topics   TopicSet   `json:"topics"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	node.Registry = "http://54.173.158.18"
	node.NpmVersion = "2.1.8"
	if !node.IsSetup() {
		Log("setting up plugins... ")
		must(node.Setup())
		Logln("done")
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errln("WARNING: command %s has already been defined", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args["name"]
		Errf("Installing plugin %s... ", name)
		must(node.InstallPackage(name))
		plugin := getPlugin(name)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("This does not appear to be a Heroku plugin, uninstalling... ")
			must(node.RemovePackage(name))
		}
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		must(err)
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		ctxJSON, err := json.Marshal(ctx)
		must(err)
		script := fmt.Sprintf(`
		require('%s')
		.commands.filter(function (command) {
			return command.topic == '%s' && command.command == '%s'
		})[0]
		.run(%s)`, module, topic, command, ctxJSON)

		cmd := node.RunScript(script)
		cmd.Stdout = Stdout
		cmd.Stdin = os.Stdin
		r, w, _ := os.Pipe()
		cmd.Stderr = w
		stderr := captureText(r)
		if err := cmd.Run(); err != nil {
			Errf("Error in %s\n", ctx.Command.Plugin)
			if Channel != "?" {
				nodeErr := fmt.Errorf("%s %s\n%s", ctx.Command.Plugin, ctx.Command, stderr.String())
				rollbar.ErrorWithStack(rollbar.ERR, nodeErr, rollbar.Stack{})
				rollbar.Wait()
			}
			// Exit with the same exit code
			Exit(getExitCode(err))
		}
	}
}

func captureText(r io.Reader) *bytes.Buffer {
	var b bytes.Buffer
	go func() {
		scanner := bufio.NewScanner(r)
		for scanner.Scan() {
			b.WriteString(scanner.Text() + "\n")
			Errln(scanner.Text())
		}
	}()
	return &b
}

func getExitCode(err error) int {
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		panic(err)
	}
	status, ok := exitErr.Sys().(syscall.WaitStatus)
	if !ok {
		panic(err)
	}
	return status.ExitStatus()
}

func getPlugin(name string) *Plugin {
	script := `console.log(JSON.stringify(require('` + name + `')))`
	cmd := node.RunScript(script)
	cmd.Stderr = Stderr
	output, err := cmd.StdoutPipe()
	must(err)
	must(cmd.Start())
	var plugin Plugin
	err = json.NewDecoder(output).Decode(&plugin)
	if err != nil {
		Errln("Error reading plugin:", name)
		return nil
	}
	must(cmd.Wait())
	for _, command := range plugin.Commands {
		command.Plugin = name
		command.Run = runFn(name, command.Topic, command.Command)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := getPlugin(name)
		if plugin != nil {
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		names = append(names, f.Name())
	}
	return names
}
<commit_msg>Require plugin name<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"

	"github.com/dickeyxxx/gode"
	"github.com/stvp/rollbar"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Topics   TopicSet   `json:"topics"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	node.Registry = "http://54.173.158.18"
	node.NpmVersion = "2.1.8"
	if !node.IsSetup() {
		Log("setting up plugins... ")
		must(node.Setup())
		Logln("done")
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errln("WARNING: command %s has already been defined", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		fmt.Println(len(name))

		must(node.InstallPackage(name))
		plugin := getPlugin(name)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("This does not appear to be a Heroku plugin, uninstalling... ")
			must(node.RemovePackage(name))
		}
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		must(err)
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		ctxJSON, err := json.Marshal(ctx)
		must(err)
		script := fmt.Sprintf(`
		require('%s')
		.commands.filter(function (command) {
			return command.topic == '%s' && command.command == '%s'
		})[0]
		.run(%s)`, module, topic, command, ctxJSON)

		cmd := node.RunScript(script)
		cmd.Stdout = Stdout
		cmd.Stdin = os.Stdin
		r, w, _ := os.Pipe()
		cmd.Stderr = w
		stderr := captureText(r)
		if err := cmd.Run(); err != nil {
			Errf("Error in %s\n", ctx.Command.Plugin)
			if Channel != "?" {
				nodeErr := fmt.Errorf("%s %s\n%s", ctx.Command.Plugin, ctx.Command, stderr.String())
				rollbar.ErrorWithStack(rollbar.ERR, nodeErr, rollbar.Stack{})
				rollbar.Wait()
			}
			// Exit with the same exit code
			Exit(getExitCode(err))
		}
	}
}

func captureText(r io.Reader) *bytes.Buffer {
	var b bytes.Buffer
	go func() {
		scanner := bufio.NewScanner(r)
		for scanner.Scan() {
			b.WriteString(scanner.Text() + "\n")
			Errln(scanner.Text())
		}
	}()
	return &b
}

func getExitCode(err error) int {
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		panic(err)
	}
	status, ok := exitErr.Sys().(syscall.WaitStatus)
	if !ok {
		panic(err)
	}
	return status.ExitStatus()
}

func getPlugin(name string) *Plugin {
	script := `console.log(JSON.stringify(require('` + name + `')))`
	cmd := node.RunScript(script)
	cmd.Stderr = Stderr
	output, err := cmd.StdoutPipe()
	must(err)
	must(cmd.Start())
	var plugin Plugin
	err = json.NewDecoder(output).Decode(&plugin)
	if err != nil {
		Errln("Error reading plugin:", name)
		return nil
	}
	must(cmd.Wait())
	for _, command := range plugin.Commands {
		command.Plugin = name
		command.Run = runFn(name, command.Topic, command.Command)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := getPlugin(name)
		if plugin != nil {
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		names = append(names, f.Name())
	}
	return names
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/golock"
	"github.com/heroku/heroku-cli/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Version  string     `json:"version"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	gode.SetRootPath(AppDir())
	setup, err := gode.IsSetup()
	PrintError(err)
	if !setup {
		setupNode()
	}
}

func setupNode() {
	Err("heroku-cli: Adding dependencies...")
	PrintError(gode.Setup())
	Errln(" done")
}

func updateNode() {
	gode.SetRootPath(AppDir())
	needsUpdate, err := gode.NeedsUpdate()
	PrintError(err)
	if needsUpdate {
		setupNode()
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins map[string]*Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
	sort.Sort(cli.Topics)
	sort.Sort(cli.Commands)
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := installPlugins(name)
		ExitIfError(err)
		plugin := getPlugin(name, true)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(gode.RemovePackage(name))
		}
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin.\nDid you run " + cyan("npm install") + "?")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("Symlinked", plugin.Name)
		AddPluginsToCache(plugin)
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := gode.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists installed plugins",
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		for _, plugin := range GetPlugins() {
			if plugin != nil && len(plugin.Commands) > 0 {
				Println(plugin.Name, plugin.Version)
			}
		}
	},
}

func runFn(plugin *Plugin, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + plugin.Name
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		checkIfPluginIsInstalled(plugin.Name)
		ctx.Dev = isPluginSymlinked(plugin.Name)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		title, _ := json.Marshal(processTitle(ctx))
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var moduleVersion = '%s';
		var topic = '%s';
		var command = '%s';
		process.title = %s;
		var ctx = %s;
		ctx.version = ctx.version + ' ' + moduleName + '/' + moduleVersion + ' node-' + process.version;
		var logPath = %s;
		process.chdir(ctx.cwd);
		function repair (name) {
			console.error('Attempting to repair ' + name + '...');
			require('child_process')
			.spawnSync('heroku', ['plugins:install', name],
			{stdio: [0,1,2]});
			console.error('Repair complete. Try running your command again.');
		}
		if (!ctx.dev) {
			process.on('uncaughtException', function (err) {
				console.error(' !   Error in ' + moduleName + ':')
				if (err.message) {
					console.error(' !   ' + err.message);
					if (err.message.indexOf('Cannot find module') != -1) {
						repair(moduleName);
						process.exit(1);
					}
				} else {
					console.error(' !   ' + err);
				}
				if (err.stack) {
					var fs = require('fs');
					var log = function (line) {
						var d = new Date().toISOString()
						.replace(/T/, ' ')
						.replace(/-/g, '/')
						.replace(/\..+/, '');
						fs.appendFileSync(logPath, d + ' ' + line + '\n');
					}
					log('Error during ' + topic + ':' + command);
					log(err.stack);
					console.error(' !   See ' + logPath + ' for more info.');
				}
				process.exit(1);
			});
		}
		if (command === '') { command = null }
		var module = require(moduleName);
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, plugin.Name, plugin.Version, topic, command, string(title), ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := gode.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = gode.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	if (!plugin.commands) plugin = {}; // not a real plugin
	var pjson  = require('` + name + `/package.json');

	plugin.name    = pjson.name;
	plugin.version = pjson.version;

	console.log(JSON.stringify(plugin))`
	cmd := gode.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := installPlugins(name); err != nil {
				panic(errors.New(name + ": " + string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	for _, command := range plugin.Commands {
		command.Plugin = plugin.Name
		command.Help = strings.TrimSpace(command.Help)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() map[string]*Plugin {
	plugins := FetchPluginCache()
	for name, plugin := range plugins {
		if plugin == nil {
			delete(plugins, name)
		} else {
			for _, command := range plugin.Commands {
				command.Run = runFn(plugin, command.Topic, command.Command)
			}
		}
	}
	return plugins
}

// PluginNames lists all the plugin names
func PluginNames() []string {
	plugins := FetchPluginCache()
	names := make([]string, 0, len(plugins))
	for _, plugin := range plugins {
		if plugin != nil {
			names = append(names, plugin.Name)
		}
	}
	return names
}

// PluginNamesNotSymlinked returns all the plugins that are not symlinked
func PluginNamesNotSymlinked() []string {
	a := PluginNames()
	b := make([]string, 0, len(a))
	for _, plugin := range a {
		if !isPluginSymlinked(plugin) {
			b = append(b, plugin)
		}
	}
	return b
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir(), "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		panic(err)
	}
	return fi.Mode()&os.ModeSymlink != 0
}

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(pluginNames...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	Errln(" done")
}

func difference(a, b []string) []string {
	res := make([]string, 0, len(a))
	for _, aa := range a {
		if !contains(b, aa) {
			res = append(res, aa)
		}
	}
	return res
}

func contains(arr []string, s string) bool {
	for _, a := range arr {
		if a == s {
			return true
		}
	}
	return false
}

func installPlugins(names ...string) error {
	for _, name := range names {
		lockfile := updateLockPath + "." + name
		LogIfError(golock.Lock(lockfile))
	}
	err := gode.InstallPackage(names...)
	plugins := make([]*Plugin, 0, len(names))
	for _, name := range names {
		plugins = append(plugins, getPlugin(name, true))
	}
	AddPluginsToCache(plugins...)
	for _, name := range names {
		lockfile := updateLockPath + "." + name
		LogIfError(golock.Unlock(lockfile))
	}
	return err
}

func checkIfPluginIsInstalled(plugin string) {
	if exists, _ := fileExists(filepath.Join(AppDir(), "node_modules", plugin)); !exists {
		installPlugins(plugin)
	}
}
<commit_msg>show if plugin is symlinked under `heroku plugins`<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/golock"
	"github.com/heroku/heroku-cli/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Version  string     `json:"version"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	gode.SetRootPath(AppDir())
	setup, err := gode.IsSetup()
	PrintError(err)
	if !setup {
		setupNode()
	}
}

func setupNode() {
	Err("heroku-cli: Adding dependencies...")
	PrintError(gode.Setup())
	Errln(" done")
}

func updateNode() {
	gode.SetRootPath(AppDir())
	needsUpdate, err := gode.NeedsUpdate()
	PrintError(err)
	if needsUpdate {
		setupNode()
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins map[string]*Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
	sort.Sort(cli.Topics)
	sort.Sort(cli.Commands)
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := installPlugins(name)
		ExitIfError(err)
		plugin := getPlugin(name, true)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(gode.RemovePackage(name))
		}
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin.\nDid you run " + cyan("npm install") + "?")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("Symlinked", plugin.Name)
		AddPluginsToCache(plugin)
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := gode.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists installed plugins",
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		for _, plugin := range GetPlugins() {
			if plugin != nil && len(plugin.Commands) > 0 {
				symlinked := ""
				if isPluginSymlinked(plugin.Name) {
					symlinked = " (symlinked)"
				}
				Println(plugin.Name, plugin.Version, symlinked)
			}
		}
	},
}

func runFn(plugin *Plugin, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + plugin.Name
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		checkIfPluginIsInstalled(plugin.Name)
		ctx.Dev = isPluginSymlinked(plugin.Name)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		title, _ := json.Marshal(processTitle(ctx))
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var moduleVersion = '%s';
		var topic = '%s';
		var command = '%s';
		process.title = %s;
		var ctx = %s;
		ctx.version = ctx.version + ' ' + moduleName + '/' + moduleVersion + ' node-' + process.version;
		var logPath = %s;
		process.chdir(ctx.cwd);
		function repair (name) {
			console.error('Attempting to repair ' + name + '...');
			require('child_process')
			.spawnSync('heroku', ['plugins:install', name],
			{stdio: [0,1,2]});
			console.error('Repair complete. Try running your command again.');
		}
		if (!ctx.dev) {
			process.on('uncaughtException', function (err) {
				console.error(' !   Error in ' + moduleName + ':')
				if (err.message) {
					console.error(' !   ' + err.message);
					if (err.message.indexOf('Cannot find module') != -1) {
						repair(moduleName);
						process.exit(1);
					}
				} else {
					console.error(' !   ' + err);
				}
				if (err.stack) {
					var fs = require('fs');
					var log = function (line) {
						var d = new Date().toISOString()
						.replace(/T/, ' ')
						.replace(/-/g, '/')
						.replace(/\..+/, '');
						fs.appendFileSync(logPath, d + ' ' + line + '\n');
					}
					log('Error during ' + topic + ':' + command);
					log(err.stack);
					console.error(' !   See ' + logPath + ' for more info.');
				}
				process.exit(1);
			});
		}
		if (command === '') { command = null }
		var module = require(moduleName);
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, plugin.Name, plugin.Version, topic, command, string(title), ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := gode.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = gode.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	if (!plugin.commands) plugin = {}; // not a real plugin
	var pjson  = require('` + name + `/package.json');

	plugin.name    = pjson.name;
	plugin.version = pjson.version;

	console.log(JSON.stringify(plugin))`
	cmd := gode.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := installPlugins(name); err != nil {
				panic(errors.New(name + ": " + string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	for _, command := range plugin.Commands {
		command.Plugin = plugin.Name
		command.Help = strings.TrimSpace(command.Help)
	}
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() map[string]*Plugin {
	plugins := FetchPluginCache()
	for name, plugin := range plugins {
		if plugin == nil {
			delete(plugins, name)
		} else {
			for _, command := range plugin.Commands {
				command.Run = runFn(plugin, command.Topic, command.Command)
			}
		}
	}
	return plugins
}

// PluginNames lists all the plugin names
func PluginNames() []string {
	plugins := FetchPluginCache()
	names := make([]string, 0, len(plugins))
	for _, plugin := range plugins {
		if plugin != nil {
			names = append(names, plugin.Name)
		}
	}
	return names
}

// PluginNamesNotSymlinked returns all the plugins that are not symlinked
func PluginNamesNotSymlinked() []string {
	a := PluginNames()
	b := make([]string, 0, len(a))
	for _, plugin := range a {
		if !isPluginSymlinked(plugin) {
			b = append(b, plugin)
		}
	}
	return b
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir(), "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		panic(err)
	}
	return fi.Mode()&os.ModeSymlink != 0
}

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	Err("heroku-cli: Installing core plugins...")
	err := installPlugins(pluginNames...)
	if err != nil {
		Errln()
		PrintError(err)
		return
	}
	Errln(" done")
}

func difference(a, b []string) []string {
	res := make([]string, 0, len(a))
	for _, aa := range a {
		if !contains(b, aa) {
			res = append(res, aa)
		}
	}
	return res
}

func contains(arr []string, s string) bool {
	for _, a := range arr {
		if a == s {
			return true
		}
	}
	return false
}

func installPlugins(names ...string) error {
	for _, name := range names {
		lockfile := updateLockPath + "." + name
		LogIfError(golock.Lock(lockfile))
	}
	err := gode.InstallPackage(names...)
	plugins := make([]*Plugin, 0, len(names))
	for _, name := range names {
		plugins = append(plugins, getPlugin(name, true))
	}
	AddPluginsToCache(plugins...)
	for _, name := range names {
		lockfile := updateLockPath + "." + name
		LogIfError(golock.Unlock(lockfile))
	}
	return err
}

func checkIfPluginIsInstalled(plugin string) {
	if exists, _ := fileExists(filepath.Join(AppDir(), "node_modules", plugin)); !exists {
		installPlugins(plugin)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"reflect"
	"strings"
)

// InOutPlugins struct for holding references to plugins
type InOutPlugins struct {
	Inputs  []io.Reader
	Outputs []io.Writer
}

// Plugins holds all the plugin objects
var Plugins *InOutPlugins = new(InOutPlugins)

// extractLimitOptions detects if plugin get called with limiter support
// Returns address and limit
func extractLimitOptions(options string) (string, string) {
	split := strings.Split(options, "|")

	if len(split) > 1 {
		return split[0], split[1]
	}

	return split[0], ""
}

// Automatically detects type of plugin and initialize it
//
// See this article if curious about relfect stuff below: http://blog.burntsushi.net/type-parametric-functions-golang
func registerPlugin(constructor interface{}, options ...interface{}) {
	vc := reflect.ValueOf(constructor)

	// Pre-processing options to make it work with reflect
	vo := []reflect.Value{}
	for _, oi := range options {
		vo = append(vo, reflect.ValueOf(oi))
	}

	// Removing limit options from path
	path, limit := extractLimitOptions(vo[0].String())

	// Writing value back without limiter "|" options
	vo[0] = reflect.ValueOf(path)

	// Calling our constructor with list of given options
	plugin := vc.Call(vo)[0].Interface()
	pluginWrapper := plugin

	if limit != "" {
		pluginWrapper = NewLimiter(plugin, limit)
	} else {
		pluginWrapper = plugin
	}

	if _, ok := plugin.(io.Reader); ok {
		Plugins.Inputs = append(Plugins.Inputs, pluginWrapper.(io.Reader))
	}

	if _, ok := plugin.(io.Writer); ok {
		Plugins.Outputs = append(Plugins.Outputs, pluginWrapper.(io.Writer))
	}
}

// InitPlugins specify and initialize all available plugins
func InitPlugins() {
	for _, options := range Settings.inputDummy {
		registerPlugin(NewDummyInput, options)
	}

	for _, options := range Settings.outputDummy {
		registerPlugin(NewDummyOutput, options)
	}

	for _, options := range Settings.inputRAW {
		registerPlugin(NewRAWInput, options)
	}

	for _, options := range Settings.inputTCP {
		registerPlugin(NewTCPInput, options)
	}

	for _, options := range Settings.outputTCP {
		registerPlugin(NewTCPOutput, options)
	}

	for _, options := range Settings.inputFile {
		registerPlugin(NewFileInput, options)
	}

	for _, options := range Settings.outputFile {
		registerPlugin(NewFileOutput, options)
	}

	for _, options := range Settings.inputHTTP {
		registerPlugin(NewHTTPInput, options)
	}

	// If we explicitly set Host header http output should not rewrite it
	// Fix: https://github.com/buger/gor/issues/174
	for _, header := range Settings.modifierConfig.headers {
		if header.Name == "Host" {
			Settings.outputHTTPConfig.OriginalHost = true
			break
		}
	}

	for _, options := range Settings.outputHTTP {
		registerPlugin(NewHTTPOutput, options, &Settings.outputHTTPConfig)
	}
}
<commit_msg>Fix tests<commit_after>package main

import (
	"io"
	"reflect"
	"strings"
)

// InOutPlugins struct for holding references to plugins
type InOutPlugins struct {
	Inputs  []io.Reader
	Outputs []io.Writer
}

// Plugins holds all the plugin objects
var Plugins *InOutPlugins = new(InOutPlugins)

// extractLimitOptions detects if plugin get called with limiter support
// Returns address and limit
func extractLimitOptions(options string) (string, string) {
	split := strings.Split(options, "|")

	if len(split) > 1 {
		return split[0], split[1]
	}

	return split[0], ""
}

// Automatically detects type of plugin and initialize it
//
// See this article if curious about relfect stuff below: http://blog.burntsushi.net/type-parametric-functions-golang
func registerPlugin(constructor interface{}, options ...interface{}) {
	vc := reflect.ValueOf(constructor)

	// Pre-processing options to make it work with reflect
	vo := []reflect.Value{}
	for _, oi := range options {
		vo = append(vo, reflect.ValueOf(oi))
	}

	// Removing limit options from path
	path, limit := extractLimitOptions(vo[0].String())

	// Writing value back without limiter "|" options
	vo[0] = reflect.ValueOf(path)

	// Calling our constructor with list of given options
	plugin := vc.Call(vo)[0].Interface()
	pluginWrapper := plugin

	if limit != "" {
		pluginWrapper = NewLimiter(plugin, limit)
	} else {
		pluginWrapper = plugin
	}

	_, isR := plugin.(io.Reader)
	_, isW := plugin.(io.Writer)

	if isR && !isW {
		Plugins.Inputs = append(Plugins.Inputs, pluginWrapper.(io.Reader))
	}

	if isW {
		Plugins.Outputs = append(Plugins.Outputs, pluginWrapper.(io.Writer))
	}
}

// InitPlugins specify and initialize all available plugins
func InitPlugins() {
	for _, options := range Settings.inputDummy {
		registerPlugin(NewDummyInput, options)
	}

	for _, options := range Settings.outputDummy {
		registerPlugin(NewDummyOutput, options)
	}

	for _, options := range Settings.inputRAW {
		registerPlugin(NewRAWInput, options)
	}

	for _, options := range Settings.inputTCP {
		registerPlugin(NewTCPInput, options)
	}

	for _, options := range Settings.outputTCP {
		registerPlugin(NewTCPOutput, options)
	}

	for _, options := range Settings.inputFile {
		registerPlugin(NewFileInput, options)
	}

	for _, options := range Settings.outputFile {
		registerPlugin(NewFileOutput, options)
	}

	for _, options := range Settings.inputHTTP {
		registerPlugin(NewHTTPInput, options)
	}

	// If we explicitly set Host header http output should not rewrite it
	// Fix: https://github.com/buger/gor/issues/174
	for _, header := range Settings.modifierConfig.headers {
		if header.Name == "Host" {
			Settings.outputHTTPConfig.OriginalHost = true
			break
		}
	}

	for _, options := range Settings.outputHTTP {
		registerPlugin(NewHTTPOutput, options, &Settings.outputHTTPConfig)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/disintegration/gift"
	"github.com/disintegration/imaging"
	"github.com/minotar/minecraft"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io"
	"math"
)

const (
	HeadX      = 8
	HeadY      = 8
	HeadWidth  = 8
	HeadHeight = 8

	HelmX      = 40
	HelmY      = 8
	HelmWidth  = 8
	HelmHeight = 8

	TorsoX      = 20
	TorsoY      = 20
	TorsoWidth  = 8
	TorsoHeight = 12

	RaX      = 44
	RaY      = 20
	RaWidth  = 4
	RaHeight = 12

	RlX      = 4
	RlY      = 20
	RlWidth  = 4
	RlHeight = 12

	LaX      = 36
	LaY      = 52
	LaWidth  = 4
	LaHeight = 12

	LlX      = 20
	LlY      = 52
	LlWidth  = 4
	LlHeight = 12

	// The height of the 'bust' relative to the width of the body (16)
	BustHeight = 16
)

type mcSkin struct {
	Processed image.Image
	minecraft.Skin
}

func (skin *mcSkin) GetHead(width int) error {
	skin.Processed = cropHead(skin.Image)
	skin.Resize(width, imaging.NearestNeighbor)
	return nil
}

func (skin *mcSkin) GetHelm(width int) error {
	skin.Processed = cropHelm(skin.Image)
	skin.Resize(width, imaging.NearestNeighbor)
	return nil
}

func (skin *mcSkin) GetBody(width int) error {
	// Check if 1.8 skin (the max Y bound should be 64)
	render18Skin := true
	bounds := skin.Image.Bounds()
	if bounds.Max.Y != 64 {
		render18Skin = false
	}

	helmImg := cropHelm(skin.Image)
	torsoImg := imaging.Crop(skin.Image, image.Rect(TorsoX, TorsoY, TorsoX+TorsoWidth, TorsoY+TorsoHeight))
	raImg := imaging.Crop(skin.Image, image.Rect(RaX, RaY, RaX+RaWidth, RaY+RaHeight))
	rlImg := imaging.Crop(skin.Image, image.Rect(RlX, RlY, RlX+RlWidth, RlY+RlHeight))

	var laImg, llImg image.Image

	// If the skin is 1.8 then we will use the left arms and legs, otherwise flip the right ones and use them.
	if render18Skin {
		laImg = imaging.Crop(skin.Image, image.Rect(LaX, LaY, LaX+LaWidth, LaY+LaHeight))
		llImg = imaging.Crop(skin.Image, image.Rect(LlX, LlY, LlX+LlWidth, LlY+LlHeight))
	} else {
		laImg = imaging.FlipH(raImg)

		llImg = imaging.FlipH(rlImg)
	}

	// Create a blank canvas for us to draw our body on
	bodyImg := image.NewNRGBA(image.Rect(0, 0, LaWidth+TorsoWidth+RaWidth, HeadHeight+TorsoHeight+LlHeight))
	// Helm
	fastDraw(bodyImg, helmImg.(*image.NRGBA), LaWidth, 0)
	// Torso
	fastDraw(bodyImg, torsoImg, LaWidth, HelmHeight)
	// Left Arm
	fastDraw(bodyImg, laImg.(*image.NRGBA), 0, HelmHeight)
	// Right Arm
	fastDraw(bodyImg, raImg, LaWidth+TorsoWidth, HelmHeight)
	// Left Leg
	fastDraw(bodyImg, llImg.(*image.NRGBA), LaWidth, HelmHeight+TorsoHeight)
	// Right Leg
	fastDraw(bodyImg, rlImg, LaWidth+LlWidth, HelmHeight+TorsoHeight)

	skin.Processed = bodyImg
	skin.Resize(width, imaging.NearestNeighbor)
	return nil
}

func (skin *mcSkin) GetBust(width int) error {
	err := skin.GetBody(LaWidth + TorsoWidth + RaWidth)
	if err != nil {
		return err
	}

	skin.Processed = imaging.Crop(skin.Processed, image.Rect(0, 0, 16, 16))
	skin.Resize(width, imaging.NearestNeighbor)
	return nil
}

func (skin *mcSkin) GetCube(width int) error {
	// Crop out the top of the head
	topFlat := imaging.Crop(skin.Image, image.Rect(8, 0, 16, 8))
	// Resize appropriately, so that it fills the `width` when rotated 45 def.
	topFlat = imaging.Resize(topFlat, int(float64(width)*math.Sqrt(2)/3+1), 0, imaging.NearestNeighbor)
	// Create the Gift filter
	filter := gift.New(
		gift.Rotate(45, color.Transparent, gift.LinearInterpolation),
	)
	bounds := filter.Bounds(topFlat.Bounds())
	top := image.NewNRGBA(bounds)
	// Draw it on the filter, then smush it!
	filter.Draw(top, topFlat)
	top = imaging.Resize(top, width, width/3, imaging.NearestNeighbor)
	// Skew the front and sides at 15 degree angles to match up with the
	// head that has been smushed
	front := cropHelm(skin.Image).(*image.NRGBA)
	side := imaging.Crop(skin.Image, image.Rect(0, 8, 8, 16))
	front = imaging.Resize(front, width/2, int(float64(width)/1.75), imaging.NearestNeighbor)
	side = imaging.Resize(side, width/2, int(float64(width)/1.75), imaging.NearestNeighbor)
	front = skewVertical(front, math.Pi/12)
	side = skewVertical(side, math.Pi/-12)

	// Create a new image to assemble upon
	skin.Processed = image.NewNRGBA(image.Rect(0, 0, width, width))
	// Draw each side
	draw.Draw(skin.Processed.(draw.Image), image.Rect(0, width/6, width/2, width), side, image.Pt(0, 0), draw.Src)
	draw.Draw(skin.Processed.(draw.Image), image.Rect(width/2, width/6, width, width), front, image.Pt(0, 0), draw.Src)
	// Draw the top we created
	draw.Draw(skin.Processed.(draw.Image), image.Rect(0, 0, width, width/3), top, image.Pt(0, 0), draw.Over)

	return nil
}

func (skin *mcSkin) WritePNG(w io.Writer) error {
	return png.Encode(w, skin.Processed)
}

func (skin *mcSkin) WriteSkin(w io.Writer) error {
	return png.Encode(w, skin.Image)
}

func (skin *mcSkin) Resize(width int, filter imaging.ResampleFilter) {
	skin.Processed = imaging.Resize(skin.Processed, width, 0, filter)
}

func cropHead(img image.Image) image.Image {
	return imaging.Crop(img, image.Rect(HeadX, HeadY, HeadX+HeadWidth, HeadY+HeadHeight))
}

func cropHelm(img image.Image) image.Image {
	headImg := cropHead(img)
	helmImg := imaging.Crop(img, image.Rect(HelmX, HelmY, HelmX+HelmWidth, HelmY+HelmHeight))

	fastDraw(headImg.(*image.NRGBA), helmImg, 0, 0)

	return headImg
}

func fastDraw(dst *image.NRGBA, src *image.NRGBA, x, y int) {
	bounds := src.Bounds()
	maxY := bounds.Max.Y
	maxX := bounds.Max.X * 4

	pointer := dst.PixOffset(x, y)
	for row := 0; row < maxY; row += 1 {
		for i := 0; i < maxX; i += 4 {
			srcPx := row*src.Stride + i
			dstPx := row*dst.Stride + i + pointer
			if src.Pix[srcPx+3] != 0 {
				dst.Pix[dstPx+0] = src.Pix[srcPx+0]
				dst.Pix[dstPx+1] = src.Pix[srcPx+1]
				dst.Pix[dstPx+2] = src.Pix[srcPx+2]
				dst.Pix[dstPx+3] = 0xFF
			}
		}
	}
}

func skewVertical(src *image.NRGBA, degrees float64) *image.NRGBA {
	bounds := src.Bounds()
	maxY := bounds.Max.Y
	maxX := bounds.Max.X * 4
	distance := float64(bounds.Max.X) * math.Tan(degrees)
	shouldFlip := false
	if distance < 0 {
		distance = -distance
		shouldFlip = true
	}

	newHeight := maxY + int(1+math.Abs(distance))
	dst := image.NewNRGBA(image.Rect(0, 0, bounds.Max.X, newHeight))

	step := distance
	for x := 0; x < maxX; x += 4 {
		for row := 0; row < maxY; row += 1 {
			srcPx := row*src.Stride + x
			dstLower := (int(step)+row)*dst.Stride + x
			dstUpper := dstLower + dst.Stride
			_, delta := math.Modf(step)

			if src.Pix[srcPx+3] != 0 {
				dst.Pix[dstLower+0] += uint8(float64(src.Pix[srcPx+0]) * (1 - delta))
				dst.Pix[dstLower+1] += uint8(float64(src.Pix[srcPx+1]) * (1 - delta))
				dst.Pix[dstLower+2] += uint8(float64(src.Pix[srcPx+2]) * (1 - delta))
				dst.Pix[dstLower+3] += uint8(float64(src.Pix[srcPx+3]) * (1 - delta))

				dst.Pix[dstUpper+0] += uint8(float64(src.Pix[srcPx+0]) * delta)
				dst.Pix[dstUpper+1] += uint8(float64(src.Pix[srcPx+1]) * delta)
				dst.Pix[dstUpper+2] += uint8(float64(src.Pix[srcPx+2]) * delta)
				dst.Pix[dstUpper+3] += uint8(float64(src.Pix[srcPx+3]) * delta)
			}
		}

		step -= distance / float64(bounds.Max.X)
	}

	if shouldFlip {
		return imaging.FlipH(dst)
	} else {
		return dst
	}
}
<commit_msg>Sides were flipped the wrong way<commit_after>package main

import (
	"github.com/disintegration/gift"
	"github.com/disintegration/imaging"
	"github.com/minotar/minecraft"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io"
	"math"
)

const (
	HeadX      = 8
	HeadY      = 8
	HeadWidth  = 8
	HeadHeight = 8

	HelmX      = 40
	HelmY      = 8
	HelmWidth  = 8
	HelmHeight = 8

	TorsoX      = 20
	TorsoY      = 20
	TorsoWidth  = 8
	TorsoHeight = 12

	RaX      = 44
	RaY      = 20
	RaWidth  = 4
	RaHeight = 12

	RlX      = 4
	RlY      = 20
	RlWidth  = 4
	RlHeight = 12

	LaX      = 36
	LaY      = 52
	LaWidth  = 4
	LaHeight = 12

	LlX      = 20
	LlY      = 52
	LlWidth  = 4
	LlHeight = 12

	// The height of the 'bust' relative to the width of the body (16)
	BustHeight = 16
)

type mcSkin struct {
	Processed image.Image
	minecraft.Skin
}

func (skin *mcSkin) GetHead(width int) error {
	skin.Processed = cropHead(skin.Image)
	skin.Resize(width, imaging.NearestNeighbor)
	return nil
}

func (skin *mcSkin) GetHelm(width int) error {
	skin.Processed = cropHelm(skin.Image)
	skin.Resize(width, imaging.NearestNeighbor)
	return nil
}

func (skin *mcSkin) GetBody(width int) error {
	// Check if 1.8 skin (the max Y bound should be 64)
	render18Skin := true
	bounds := skin.Image.Bounds()
	if bounds.Max.Y != 64 {
		render18Skin = false
	}

	helmImg := cropHelm(skin.Image)
	torsoImg := imaging.Crop(skin.Image, image.Rect(TorsoX, TorsoY, TorsoX+TorsoWidth, TorsoY+TorsoHeight))
	raImg := imaging.Crop(skin.Image, image.Rect(RaX, RaY, RaX+RaWidth, RaY+RaHeight))
	rlImg := imaging.Crop(skin.Image, image.Rect(RlX, RlY, RlX+RlWidth, RlY+RlHeight))

	var laImg, llImg image.Image

	// If the skin is 1.8 then we will use the left arms and legs, otherwise flip the right ones and use them.
	if render18Skin {
		laImg = imaging.Crop(skin.Image, image.Rect(LaX, LaY, LaX+LaWidth, LaY+LaHeight))
		llImg = imaging.Crop(skin.Image, image.Rect(LlX, LlY, LlX+LlWidth, LlY+LlHeight))
	} else {
		laImg = imaging.FlipH(raImg)

		llImg = imaging.FlipH(rlImg)
	}

	// Create a blank canvas for us to draw our body on
	bodyImg := image.NewNRGBA(image.Rect(0, 0, LaWidth+TorsoWidth+RaWidth, HeadHeight+TorsoHeight+LlHeight))
	// Helm
	fastDraw(bodyImg, helmImg.(*image.NRGBA), LaWidth, 0)
	// Torso
	fastDraw(bodyImg, torsoImg, LaWidth, HelmHeight)
	// Left Arm
	fastDraw(bodyImg, laImg.(*image.NRGBA), 0, HelmHeight)
	// Right Arm
	fastDraw(bodyImg, raImg, LaWidth+TorsoWidth, HelmHeight)
	// Left Leg
	fastDraw(bodyImg, llImg.(*image.NRGBA), LaWidth, HelmHeight+TorsoHeight)
	// Right Leg
	fastDraw(bodyImg, rlImg, LaWidth+LlWidth, HelmHeight+TorsoHeight)

	skin.Processed = bodyImg
	skin.Resize(width, imaging.NearestNeighbor)
	return nil
}

func (skin *mcSkin) GetBust(width int) error {
	err := skin.GetBody(LaWidth + TorsoWidth + RaWidth)
	if err != nil {
		return err
	}

	skin.Processed = imaging.Crop(skin.Processed, image.Rect(0, 0, 16, 16))
	skin.Resize(width, imaging.NearestNeighbor)
	return nil
}

func (skin *mcSkin) GetCube(width int) error {
	// Crop out the top of the head
	topFlat := imaging.Crop(skin.Image, image.Rect(8, 0, 16, 8))
	// Resize appropriately, so that it fills the `width` when rotated 45 def.
	topFlat = imaging.Resize(topFlat, int(float64(width)*math.Sqrt(2)/3+1), 0, imaging.NearestNeighbor)
	// Create the Gift filter
	filter := gift.New(
		gift.Rotate(45, color.Transparent, gift.LinearInterpolation),
	)
	bounds := filter.Bounds(topFlat.Bounds())
	top := image.NewNRGBA(bounds)
	// Draw it on the filter, then smush it!
	filter.Draw(top, topFlat)
	top = imaging.Resize(top, width, width/3, imaging.NearestNeighbor)
	// Skew the front and sides at 15 degree angles to match up with the
	// head that has been smushed
	front := cropHelm(skin.Image).(*image.NRGBA)
	side := imaging.Crop(skin.Image, image.Rect(0, 8, 8, 16))
	front = imaging.Resize(front, width/2, int(float64(width)/1.75), imaging.NearestNeighbor)
	side = imaging.Resize(side, width/2, int(float64(width)/1.75), imaging.NearestNeighbor)
	front = skewVertical(front, math.Pi/12)
	side = skewVertical(imaging.FlipH(side), math.Pi/-12)

	// Create a new image to assemble upon
	skin.Processed = image.NewNRGBA(image.Rect(0, 0, width, width))
	// Draw each side
	draw.Draw(skin.Processed.(draw.Image), image.Rect(0, width/6, width/2, width), side, image.Pt(0, 0), draw.Src)
	draw.Draw(skin.Processed.(draw.Image), image.Rect(width/2, width/6, width, width), front, image.Pt(0, 0), draw.Src)
	// Draw the top we created
	draw.Draw(skin.Processed.(draw.Image), image.Rect(0, 0, width, width/3), top, image.Pt(0, 0), draw.Over)

	return nil
}

func (skin *mcSkin) WritePNG(w io.Writer) error {
	return png.Encode(w, skin.Processed)
}

func (skin *mcSkin) WriteSkin(w io.Writer) error {
	return png.Encode(w, skin.Image)
}

func (skin *mcSkin) Resize(width int, filter imaging.ResampleFilter) {
	skin.Processed = imaging.Resize(skin.Processed, width, 0, filter)
}

func cropHead(img image.Image) image.Image {
	return imaging.Crop(img, image.Rect(HeadX, HeadY, HeadX+HeadWidth, HeadY+HeadHeight))
}

func cropHelm(img image.Image) image.Image {
	headImg := cropHead(img)
	helmImg := imaging.Crop(img, image.Rect(HelmX, HelmY, HelmX+HelmWidth, HelmY+HelmHeight))

	fastDraw(headImg.(*image.NRGBA), helmImg, 0, 0)

	return headImg
}

func fastDraw(dst *image.NRGBA, src *image.NRGBA, x, y int) {
	bounds := src.Bounds()
	maxY := bounds.Max.Y
	maxX := bounds.Max.X * 4

	pointer := dst.PixOffset(x, y)
	for row := 0; row < maxY; row += 1 {
		for i := 0; i < maxX; i += 4 {
			srcPx := row*src.Stride + i
			dstPx := row*dst.Stride + i + pointer
			if src.Pix[srcPx+3] != 0 {
				dst.Pix[dstPx+0] = src.Pix[srcPx+0]
				dst.Pix[dstPx+1] = src.Pix[srcPx+1]
				dst.Pix[dstPx+2] = src.Pix[srcPx+2]
				dst.Pix[dstPx+3] = 0xFF
			}
		}
	}
}

func skewVertical(src *image.NRGBA, degrees float64) *image.NRGBA {
	bounds := src.Bounds()
	maxY := bounds.Max.Y
	maxX := bounds.Max.X * 4
	distance := float64(bounds.Max.X) * math.Tan(degrees)
	shouldFlip := false
	if distance < 0 {
		distance = -distance
		shouldFlip = true
	}

	newHeight := maxY + int(1+math.Abs(distance))
	dst := image.NewNRGBA(image.Rect(0, 0, bounds.Max.X, newHeight))

	step := distance
	for x := 0; x < maxX; x += 4 {
		for row := 0; row < maxY; row += 1 {
			srcPx := row*src.Stride + x
			dstLower := (int(step)+row)*dst.Stride + x
			dstUpper := dstLower + dst.Stride
			_, delta := math.Modf(step)

			if src.Pix[srcPx+3] != 0 {
				dst.Pix[dstLower+0] += uint8(float64(src.Pix[srcPx+0]) * (1 - delta))
				dst.Pix[dstLower+1] += uint8(float64(src.Pix[srcPx+1]) * (1 - delta))
				dst.Pix[dstLower+2] += uint8(float64(src.Pix[srcPx+2]) * (1 - delta))
				dst.Pix[dstLower+3] += uint8(float64(src.Pix[srcPx+3]) * (1 - delta))

				dst.Pix[dstUpper+0] += uint8(float64(src.Pix[srcPx+0]) * delta)
				dst.Pix[dstUpper+1] += uint8(float64(src.Pix[srcPx+1]) * delta)
				dst.Pix[dstUpper+2] += uint8(float64(src.Pix[srcPx+2]) * delta)
				dst.Pix[dstUpper+3] += uint8(float64(src.Pix[srcPx+3]) * delta)
			}
		}

		step -= distance / float64(bounds.Max.X)
	}

	if shouldFlip {
		return imaging.FlipH(dst)
	} else {
		return dst
	}
}
<|endoftext|>


<commit_before>// Package script is a library facilitating the creation of programs that resemble
// bash scripts.
package script

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

// ProcessResult contains the results of a process execution be it successful or not.
type ProcessResult struct {
	Cmd          *exec.Cmd
	ProcessState *os.ProcessState
	ProcessError error
	stdoutBuffer *bytes.Buffer
	stderrBuffer *bytes.Buffer
}

// NewProcessResult creates a new empty ProcessResult
func NewProcessResult() *ProcessResult {
	p := &ProcessResult{}
	p.stdoutBuffer = bytes.NewBuffer(make([]byte, 0, 100))
	p.stderrBuffer = bytes.NewBuffer(make([]byte, 0, 100))
	return p
}

// Output returns a string representation of the output of the process denoted
// by this struct.
func (pr *ProcessResult) Output() string {
	return pr.stdoutBuffer.String()
}

// Error returns a string representation of the stderr output of the process denoted
// by this struct.
func (pr *ProcessResult) Error() string {
	return pr.stderrBuffer.String()
}

// Successful returns true iff the process denoted by this struct was run
// successfully. Success is defined as the exit code being set to 0.
func (pr *ProcessResult) Successful() bool {
	fmt.Println(pr.ExitCode())
	return pr.ExitCode() == 0
}

// StateString returns a string representation of the process denoted by
// this struct
func (pr *ProcessResult) StateString() string {
	state := pr.ProcessState
	return fmt.Sprintf("PID: %q, Exited: %t, Exit Code: %q, Success: %t, User Time: %q", state.Pid(), state.Exited(), pr.ExitCode(), state.Success(), state.UserTime())
}

// ExitCode returns the exit code of the command denoted by this struct
func (pr *ProcessResult) ExitCode() int {
	var waitStatus syscall.WaitStatus
	if exitError, ok := pr.ProcessError.(*exec.ExitError); ok {
		waitStatus = exitError.Sys().(syscall.WaitStatus)
	} else {
		waitStatus = pr.ProcessState.Sys().(syscall.WaitStatus)
	}
	return waitStatus.ExitStatus()
}

// CommandPath finds the full path of a binary given its name. This requires the wich command to be present in the system.
func (c *Context) CommandPath(name string) string {
	cmd := exec.Command("which", name)
	cmdOutput, err := cmd.Output()
	if err != nil {
		return ""
	}
	return strings.Trim(string(cmdOutput), "\n")
}

// CommandExists checks if a given binary exists in PATH.
func (c *Context) CommandExists(name string) bool {
	return c.CommandPath(name) != ""
}

// MustCommandExist ensures a given binary exists in PATH, otherwise panics.
func (c *Context) MustCommandExist(name string) {
	if !c.CommandExists(name) {
		panic(fmt.Errorf("Command %s is not available. Please make sure it is installed and accessible.", name))
	}
}

// ExecuteDebug executes a system command, stdout and stderr are piped
func (c *Context) ExecuteDebug(name string, args ...string) (pr *ProcessResult, err error) {
	pr, err = c.Execute(false, false, name, args...)
	return
}

// ExecuteSilent executes a  system command without outputting stdout (it is
// still captured and can be retrieved using the returned ProcessResult)
func (c *Context) ExecuteSilent(name string, args ...string) (pr *ProcessResult, err error) {
	pr, err = c.Execute(true, false, name, args...)
	return
}

// ExecuteFullySilent executes a system command without outputting stdout or
// stderr (both are still captured and can be retrieved using the returned ProcessResult)
func (c *Context) ExecuteFullySilent(name string, args ...string) (pr *ProcessResult, err error) {
	pr, err = c.Execute(true, true, name, args...)
	return
}

// MustExecuteDebug ensures a system command to be executed, otherwise panics
func (c *Context) MustExecuteDebug(name string, args ...string) (pr *ProcessResult) {
	pr, err := c.Execute(false, false, name, args...)
	if err != nil {
		panic(err)
	}
	return
}

// MustExecuteSilent ensures a system command to be executed without outputting
// stdout, otherwise panics
func (c *Context) MustExecuteSilent(name string, args ...string) (pr *ProcessResult) {
	pr, err := c.ExecuteSilent(name, args...)
	if err != nil {
		panic(err)
	}
	return
}

// MustExecuteFullySilent ensures a system command to be executed without
// outputting stdout and stderr, otherwise panics
func (c *Context) MustExecuteFullySilent(name string, args ...string) (pr *ProcessResult) {
	pr, err := c.ExecuteFullySilent(name, args...)
	if err != nil {
		panic(err)
	}
	return
}

// Execute executes a system command with configurable stdout and stderr output
func (c *Context) Execute(stdoutSilent bool, stderrSilent bool, name string, args ...string) (pr *ProcessResult, err error) {
	cmd, pr := c.prepareCommand(stdoutSilent, stderrSilent, name, args...)

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	err = cmd.Wait()

	pr.ProcessError = err

	return pr, err
}

// ExecuteDetached executes the given command in this context in the background (detached). This means the script execution instantly continues.
func (c *Context) ExecuteDetached(name string, args ...string) (cmd *exec.Cmd, pr *ProcessResult, err error) {
	cmd, pr = c.prepareCommand(true, true, name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}
	err = cmd.Start()
	return
}

func (c Context) prepareCommand(stdoutSilent bool, stderrSilent bool, name string, args ...string) (*exec.Cmd, *ProcessResult) {
	pr := NewProcessResult()

	cmd := exec.Command(name, args...)
	pr.Cmd = cmd
	pr.ProcessState = cmd.ProcessState

	cmd.Dir = c.workingDir
	cmd.Env = c.getFullEnv()

	if stderrSilent {
		cmd.Stderr = pr.stderrBuffer
	} else {
		cmd.Stderr = io.MultiWriter(os.Stderr, pr.stderrBuffer)
	}
	if stderrSilent {
		cmd.Stdout = pr.stdoutBuffer
	} else {
		cmd.Stdout = io.MultiWriter(os.Stdout, pr.stdoutBuffer)
	}
	return cmd, pr
}
<commit_msg>Simplify<commit_after>// Package script is a library facilitating the creation of programs that resemble
// bash scripts.
package script

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

// ProcessResult contains the results of a process execution be it successful or not.
type ProcessResult struct {
	Cmd          *exec.Cmd
	ProcessState *os.ProcessState
	ProcessError error
	stdoutBuffer *bytes.Buffer
	stderrBuffer *bytes.Buffer
}

// NewProcessResult creates a new empty ProcessResult
func NewProcessResult() *ProcessResult {
	p := &ProcessResult{}
	p.stdoutBuffer = bytes.NewBuffer(make([]byte, 0, 100))
	p.stderrBuffer = bytes.NewBuffer(make([]byte, 0, 100))
	return p
}

// Output returns a string representation of the output of the process denoted
// by this struct.
func (pr *ProcessResult) Output() string {
	return pr.stdoutBuffer.String()
}

// Error returns a string representation of the stderr output of the process denoted
// by this struct.
func (pr *ProcessResult) Error() string {
	return pr.stderrBuffer.String()
}

// Successful returns true iff the process denoted by this struct was run
// successfully. Success is defined as the exit code being set to 0.
func (pr *ProcessResult) Successful() bool {
	fmt.Println(pr.ExitCode())
	return pr.ExitCode() == 0
}

// StateString returns a string representation of the process denoted by
// this struct
func (pr *ProcessResult) StateString() string {
	state := pr.ProcessState
	return fmt.Sprintf("PID: %q, Exited: %t, Exit Code: %q, Success: %t, User Time: %q", state.Pid(), state.Exited(), pr.ExitCode(), state.Success(), state.UserTime())
}

// ExitCode returns the exit code of the command denoted by this struct
func (pr *ProcessResult) ExitCode() int {
	var waitStatus syscall.WaitStatus
	if exitError, ok := pr.ProcessError.(*exec.ExitError); ok {
		waitStatus = exitError.Sys().(syscall.WaitStatus)
	} else {
		waitStatus = pr.ProcessState.Sys().(syscall.WaitStatus)
	}
	return waitStatus.ExitStatus()
}

// CommandPath finds the full path of a binary given its name. This requires the wich command to be present in the system.
func (c *Context) CommandPath(name string) string {
	cmd := exec.Command("which", name)
	cmdOutput, err := cmd.Output()
	if err != nil {
		return ""
	}
	return strings.Trim(string(cmdOutput), "\n")
}

// CommandExists checks if a given binary exists in PATH.
func (c *Context) CommandExists(name string) bool {
	return c.CommandPath(name) != ""
}

// MustCommandExist ensures a given binary exists in PATH, otherwise panics.
func (c *Context) MustCommandExist(name string) {
	if !c.CommandExists(name) {
		panic(fmt.Errorf("Command %s is not available. Please make sure it is installed and accessible.", name))
	}
}

// ExecuteDebug executes a system command, stdout and stderr are piped
func (c *Context) ExecuteDebug(name string, args ...string) (pr *ProcessResult, err error) {
	pr, err = c.Execute(false, false, name, args...)
	return
}

// ExecuteSilent executes a  system command without outputting stdout (it is
// still captured and can be retrieved using the returned ProcessResult)
func (c *Context) ExecuteSilent(name string, args ...string) (pr *ProcessResult, err error) {
	pr, err = c.Execute(true, false, name, args...)
	return
}

// ExecuteFullySilent executes a system command without outputting stdout or
// stderr (both are still captured and can be retrieved using the returned ProcessResult)
func (c *Context) ExecuteFullySilent(name string, args ...string) (pr *ProcessResult, err error) {
	pr, err = c.Execute(true, true, name, args...)
	return
}

// MustExecuteDebug ensures a system command to be executed, otherwise panics
func (c *Context) MustExecuteDebug(name string, args ...string) (pr *ProcessResult) {
	pr, err := c.Execute(false, false, name, args...)
	if err != nil {
		panic(err)
	}
	return
}

// MustExecuteSilent ensures a system command to be executed without outputting
// stdout, otherwise panics
func (c *Context) MustExecuteSilent(name string, args ...string) (pr *ProcessResult) {
	pr, err := c.ExecuteSilent(name, args...)
	if err != nil {
		panic(err)
	}
	return
}

// MustExecuteFullySilent ensures a system command to be executed without
// outputting stdout and stderr, otherwise panics
func (c *Context) MustExecuteFullySilent(name string, args ...string) (pr *ProcessResult) {
	pr, err := c.ExecuteFullySilent(name, args...)
	if err != nil {
		panic(err)
	}
	return
}

// Execute executes a system command with configurable stdout and stderr output
func (c *Context) Execute(stdoutSilent bool, stderrSilent bool, name string, args ...string) (pr *ProcessResult, err error) {
	cmd, pr := c.prepareCommand(stdoutSilent, stderrSilent, name, args...)

	err = cmd.Start()
	if err != nil {
		return
	}
	err = cmd.Wait()
	pr.ProcessError = err

	return
}

// ExecuteDetached executes the given command in this context in the background (detached). This means the script execution instantly continues.
func (c *Context) ExecuteDetached(name string, args ...string) (cmd *exec.Cmd, pr *ProcessResult, err error) {
	cmd, pr = c.prepareCommand(true, true, name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}
	err = cmd.Start()
	return
}

func (c Context) prepareCommand(stdoutSilent bool, stderrSilent bool, name string, args ...string) (*exec.Cmd, *ProcessResult) {
	pr := NewProcessResult()

	cmd := exec.Command(name, args...)
	pr.Cmd = cmd
	pr.ProcessState = cmd.ProcessState

	cmd.Dir = c.workingDir
	cmd.Env = c.getFullEnv()

	if stderrSilent {
		cmd.Stderr = pr.stderrBuffer
	} else {
		cmd.Stderr = io.MultiWriter(os.Stderr, pr.stderrBuffer)
	}
	if stderrSilent {
		cmd.Stdout = pr.stdoutBuffer
	} else {
		cmd.Stdout = io.MultiWriter(os.Stdout, pr.stdoutBuffer)
	}
	return cmd, pr
}
<|endoftext|>


<commit_before>package cortexbot

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"

	valid "github.com/asaskevich/govalidator"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	cortex "github.com/ilyaglow/go-cortex/v3"
)

type updateMeta struct {
	from   *tgbotapi.User
	chatID int64
	msgID  int
	data   string
	typ    string
}

func newUpdateMeta(update *tgbotapi.Update) (*updateMeta, error) {
	if update.CallbackQuery != nil {
		return &updateMeta{
			from:   update.CallbackQuery.From,
			chatID: update.CallbackQuery.Message.Chat.ID,
			msgID:  update.CallbackQuery.Message.MessageID,
			data:   update.CallbackQuery.Data,
			typ:    "callback",
		}, nil
	}

	if update.Message != nil {
		return &updateMeta{
			from:   update.Message.From,
			chatID: update.Message.Chat.ID,
			msgID:  update.Message.MessageID,
			data:   update.Message.Text,
			typ:    "message",
		}, nil
	}

	return nil, fmt.Errorf("unknown update type: %v", update)
}

// sendReport sends a report depends on success
func (c *Cortexbot) sendReport(r *cortex.Report, callback *tgbotapi.CallbackQuery) error {
	if r.Status == "Failure" {
		msg := tgbotapi.NewMessage(callback.Message.Chat.ID, fmt.Sprintf("Analyzer %s failed on %s: %s", r.AnalyzerName, r.Data, r.ReportBody.ErrorMessage))
		msg.ReplyToMessageID = callback.Message.MessageID
		if _, err := c.Bot.Send(msg); err != nil {
			return err
		}
	}

	// Send JSON file with full report and taxonomies
	tr, _ := json.MarshalIndent(r, "", "  ")

	fb := tgbotapi.FileBytes{
		Name:  fmt.Sprintf("%s-%s.log", r.AnalyzerName, r.ID),
		Bytes: tr,
	}

	attachment := tgbotapi.NewDocumentUpload(callback.Message.Chat.ID, fb)
	attachment.ReplyToMessageID = callback.Message.ReplyToMessage.MessageID
	attachment.Caption = buildTaxonomies(r.Taxonomies())

	_, err := c.Bot.Send(attachment)
	return err
}

func (c *Cortexbot) processUpdate(update *tgbotapi.Update) error {
	meta, err := newUpdateMeta(update)
	if err != nil {
		return err
	}

	log.Printf(
		"%s: [%s:%d] %s",
		meta.typ,
		meta.from.UserName,
		meta.from.ID,
		meta.data,
	)

	msg := tgbotapi.NewMessage(meta.chatID, "")
	msg.ReplyToMessageID = meta.msgID

	isCmd := update.Message != nil && update.Message.IsCommand()
	var cmd string
	if isCmd {
		cmd = update.Message.Command()
	}

	if cmd == "login" && c.noAdmin() {
		if update.Message.CommandArguments() != c.Password {
			msg.Text = "Wrong password"
			_, err = c.Bot.Send(msg)
			return err
		}

		u := &User{
			ID:    meta.from.ID,
			Admin: 1,
			About: meta.from.String(),
		}
		err = c.addUser(u)
		if err != nil {
			msg.Text = fmt.Sprintf("Can't register: %s", err.Error())
			_, err = c.Bot.Send(msg)
			return err
		}
		log.Printf("Registered new user %d", meta.from.ID)

		msg.Text = "Success"
		_, err = c.Bot.Send(msg)
		return err
	}

	// user initiates interaction.
	if cmd == "start" {
		if c.noAdmin() {
			msg.Text = "No admin assigned. Try /login password"
			_, err = c.Bot.Send(msg)
			return err
		}

		if c.CheckAuth(meta.from) {
			msg.Text = "Already logged in, you can send indicators"
			_, err := c.Bot.Send(msg)
			return err
		}

		msg.Text = "Forward the next message to the bot admin"
		_, err := c.Bot.Send(msg)
		if err != nil {
			return err
		}
		msg = tgbotapi.NewMessage(meta.chatID, "")
		msg.Text = fmt.Sprintf("/approve %d %s", meta.from.ID, meta.from.String())
		_, err = c.Bot.Send(msg)
		return err
	}

	if c.CheckAdmin(meta.from) && cmd == "approve" {
		parts := strings.SplitN(update.Message.Text, " ", 3)
		if len(parts) < 3 {
			msg.Text = fmt.Sprintf("Not enough parameters to approve: %s", update.Message.Text)
			_, err = c.Bot.Send(msg)
			return err
		}
		id, err := strconv.ParseInt(parts[1], 10, 32)
		u := &User{
			ID:    int(id),
			Admin: 0,
			About: parts[2],
		}
		err = c.addUser(u)
		if err != nil {
			msg.Text = fmt.Sprintf("can't create user: %s", err.Error())
			_, err = c.Bot.Send(msg)
			if err != nil {
				return err
			}
		}
		msg.Text = "Successfully approved"
		_, err = c.Bot.Send(msg)
		return err
	}

	// auth guard
	if !c.CheckAuth(meta.from) {
		msg.Text = "Non-authorized action, type /start to login"
		_, err := c.Bot.Send(msg)
		return err
	}

	switch meta.typ {
	case "callback":
		if err := c.processCallback(update.CallbackQuery); err != nil {
			return err
		}

		cbcfg := tgbotapi.NewCallback(update.CallbackQuery.ID, "")
		_, err := c.Bot.AnswerCallbackQuery(cbcfg)
		return err
	case "message":
		return c.processMessage(update.Message)
	}

	return nil
}

// processCallback analyzes observables with a selected set of analyzers
func (c *Cortexbot) processCallback(callback *tgbotapi.CallbackQuery) error {
	var j cortex.Observable
	var err error

	if callback.Message.ReplyToMessage.Document != nil {
		link, err := c.Bot.GetFileDirectURL(callback.Message.ReplyToMessage.Document.FileID)
		if err != nil {
			log.Println("Can't get direct link to file")
			return err
		}

		j, err = newFileArtifactFromURL(link, callback.Message.ReplyToMessage.Document.FileName, c.TLP, c.PAP, c.Bot.Client)
		if err != nil {
			log.Println(err.Error())
			return err
		}
	} else {
		j = newArtifact(callback.Message.ReplyToMessage.Text, c.TLP, c.PAP)
	}

	switch callback.Data {
	case "all":
		mul := c.Cortex.Analyzers.NewMultiRun(context.Background(), c.Timeout)
		mul.OnReport = func(r *cortex.Report) {
			err = c.sendReport(r, callback)
			log.Println(err)
		}

		mul.OnError = func(e error, o cortex.Observable, a *cortex.Analyzer) {
			log.Println(fmt.Sprintf("Cortex analyzer %s failed on data %s with an error: %s", a.Name, o.Description(), e.Error()))
		}

		err = mul.Do(j)
		if err != nil {
			return err
		}
	case "close":
		kb := showButton()
		edit := tgbotapi.NewEditMessageReplyMarkup(callback.Message.Chat.ID, callback.Message.MessageID, *kb)
		if _, err := c.Bot.Send(edit); err != nil {
			return err
		}
	case "show":
		kb, err := c.analyzersButtons(dataType(callback.Message.ReplyToMessage.Text))
		if err != nil {
			return err
		}
		edit := tgbotapi.NewEditMessageReplyMarkup(callback.Message.Chat.ID, callback.Message.MessageID, *kb)
		if _, err := c.Bot.Send(edit); err != nil {
			return err
		}
	default:
		r, err := c.Cortex.Analyzers.Run(context.Background(), callback.Data, j, c.Timeout)
		if err != nil {
			msg := tgbotapi.NewMessage(callback.Message.Chat.ID, fmt.Sprintf("%s failed: %s", callback.Data, err.Error()))
			msg.ReplyToMessageID = callback.Message.MessageID
			if _, err := c.Bot.Send(msg); err != nil {
				return err
			}
		} else {
			err = c.sendReport(r, callback)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// analyzersButtons returns a markup of analyzers as buttons
func (c *Cortexbot) analyzersButtons(datatype string) (*tgbotapi.InlineKeyboardMarkup, error) {
	analyzers, _, err := c.Cortex.Analyzers.ListByType(context.Background(), datatype)
	if err != nil {
		return nil, err
	}

	var names []string
	for i := range analyzers {
		names = append(names, analyzers[i].Name)
	}
	sort.Strings(names)

	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("All", "all"),
	})

	for _, n := range names {
		var buttons []tgbotapi.InlineKeyboardButton
		b := tgbotapi.NewInlineKeyboardButtonData(n, n)
		buttons = append(buttons, b)
		rows = append(rows, buttons)
	}

	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("Close", "close"),
	})

	markup := tgbotapi.NewInlineKeyboardMarkup(rows...)
	return &markup, nil
}

// showButton returns only one button, used on close callback
func showButton() *tgbotapi.InlineKeyboardMarkup {
	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("Show", "show"),
	})

	markup := tgbotapi.NewInlineKeyboardMarkup(rows...)
	return &markup
}

// processMessage asks Cortex about data submitted by a user
func (c *Cortexbot) processMessage(input *tgbotapi.Message) error {
	msg := tgbotapi.NewMessage(input.Chat.ID, "Select analyzer to run. Choose <All> to run all of them.")
	msg.ReplyToMessageID = input.MessageID

	var dt string

	if input.Document != nil {
		dt = "file"
	} else {
		dt = dataType(input.Text)
	}

	bmarkup, err := c.analyzersButtons(dt)
	if err != nil {
		return err
	}
	msg.ReplyMarkup = bmarkup

	_, err = c.Bot.Send(msg)
	if err != nil {
		return err
	}

	return nil
}

// buildTaxonomies joins taxonomies in one formatted string
// Every taxonomy is separated with two spaces from each other
func buildTaxonomies(txs []cortex.Taxonomy) string {
	var stats []string

	for _, t := range txs {
		stats = append(stats, fmt.Sprintf("%s:%s = %v", t.Namespace, t.Predicate, t.Value))
	}
	return strings.Join(stats, "\n")
}

// newArtifact makes an Artifact depends on its type
func newArtifact(s string, tlp cortex.TLP, pap cortex.PAP) cortex.Observable {
	return &cortex.Task{
		Data:     s,
		DataType: dataType(s),
		TLP:      &tlp,
		PAP:      &pap,
	}
}

func dataType(d string) (t string) {
	if valid.IsIP(d) {
		t = "ip"
	} else if IsDNSName(d) {
		t = "domain"
	} else if IsHash(d) {
		t = "hash"
	} else if valid.IsEmail(d) {
		t = "mail"
	} else if valid.IsURL(d) {
		t = "url"
	} else {
		t = "other"
	}

	return t
}

// newFileArtifactFromURL makes a FileArtifact from URL
func newFileArtifactFromURL(link, fname string, tlp cortex.TLP, pap cortex.PAP, client *http.Client) (cortex.Observable, error) {
	req, err := http.NewRequest("GET", link, nil)
	if err != nil {
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	return &cortex.FileTask{
		FileTaskMeta: cortex.FileTaskMeta{
			DataType: "file",
			TLP:      &tlp,
			PAP:      &pap,
		},
		FileName: fname,
		Reader:   resp.Body,
	}, nil
}
<commit_msg>Do not report if error occurred<commit_after>package cortexbot

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"

	valid "github.com/asaskevich/govalidator"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	cortex "github.com/ilyaglow/go-cortex/v3"
)

type updateMeta struct {
	from   *tgbotapi.User
	chatID int64
	msgID  int
	data   string
	typ    string
}

func newUpdateMeta(update *tgbotapi.Update) (*updateMeta, error) {
	if update.CallbackQuery != nil {
		return &updateMeta{
			from:   update.CallbackQuery.From,
			chatID: update.CallbackQuery.Message.Chat.ID,
			msgID:  update.CallbackQuery.Message.MessageID,
			data:   update.CallbackQuery.Data,
			typ:    "callback",
		}, nil
	}

	if update.Message != nil {
		return &updateMeta{
			from:   update.Message.From,
			chatID: update.Message.Chat.ID,
			msgID:  update.Message.MessageID,
			data:   update.Message.Text,
			typ:    "message",
		}, nil
	}

	return nil, fmt.Errorf("unknown update type: %v", update)
}

// sendReport sends a report depends on success
func (c *Cortexbot) sendReport(r *cortex.Report, callback *tgbotapi.CallbackQuery) error {
	if r.Status == "Failure" {
		msg := tgbotapi.NewMessage(callback.Message.Chat.ID, fmt.Sprintf("Analyzer %s failed on %s: %s", r.AnalyzerName, r.Data, r.ReportBody.ErrorMessage))
		msg.ReplyToMessageID = callback.Message.MessageID
		_, err := c.Bot.Send(msg)

		return err
	}

	// Send JSON file with full report and taxonomies
	tr, _ := json.MarshalIndent(r, "", "  ")

	fb := tgbotapi.FileBytes{
		Name:  fmt.Sprintf("%s-%s.log", r.AnalyzerName, r.ID),
		Bytes: tr,
	}

	attachment := tgbotapi.NewDocumentUpload(callback.Message.Chat.ID, fb)
	attachment.ReplyToMessageID = callback.Message.ReplyToMessage.MessageID
	attachment.Caption = buildTaxonomies(r.Taxonomies())

	_, err := c.Bot.Send(attachment)
	return err
}

func (c *Cortexbot) processUpdate(update *tgbotapi.Update) error {
	meta, err := newUpdateMeta(update)
	if err != nil {
		return err
	}

	log.Printf(
		"%s: [%s:%d] %s",
		meta.typ,
		meta.from.UserName,
		meta.from.ID,
		meta.data,
	)

	msg := tgbotapi.NewMessage(meta.chatID, "")
	msg.ReplyToMessageID = meta.msgID

	isCmd := update.Message != nil && update.Message.IsCommand()
	var cmd string
	if isCmd {
		cmd = update.Message.Command()
	}

	if cmd == "login" && c.noAdmin() {
		if update.Message.CommandArguments() != c.Password {
			msg.Text = "Wrong password"
			_, err = c.Bot.Send(msg)
			return err
		}

		u := &User{
			ID:    meta.from.ID,
			Admin: 1,
			About: meta.from.String(),
		}
		err = c.addUser(u)
		if err != nil {
			msg.Text = fmt.Sprintf("Can't register: %s", err.Error())
			_, err = c.Bot.Send(msg)
			return err
		}
		log.Printf("Registered new user %d", meta.from.ID)

		msg.Text = "Success"
		_, err = c.Bot.Send(msg)
		return err
	}

	// user initiates interaction.
	if cmd == "start" {
		if c.noAdmin() {
			msg.Text = "No admin assigned. Try /login password"
			_, err = c.Bot.Send(msg)
			return err
		}

		if c.CheckAuth(meta.from) {
			msg.Text = "Already logged in, you can send indicators"
			_, err := c.Bot.Send(msg)
			return err
		}

		msg.Text = "Forward the next message to the bot admin"
		_, err := c.Bot.Send(msg)
		if err != nil {
			return err
		}
		msg = tgbotapi.NewMessage(meta.chatID, "")
		msg.Text = fmt.Sprintf("/approve %d %s", meta.from.ID, meta.from.String())
		_, err = c.Bot.Send(msg)
		return err
	}

	if c.CheckAdmin(meta.from) && cmd == "approve" {
		parts := strings.SplitN(update.Message.Text, " ", 3)
		if len(parts) < 3 {
			msg.Text = fmt.Sprintf("Not enough parameters to approve: %s", update.Message.Text)
			_, err = c.Bot.Send(msg)
			return err
		}
		id, err := strconv.ParseInt(parts[1], 10, 32)
		u := &User{
			ID:    int(id),
			Admin: 0,
			About: parts[2],
		}
		err = c.addUser(u)
		if err != nil {
			msg.Text = fmt.Sprintf("can't create user: %s", err.Error())
			_, err = c.Bot.Send(msg)
			if err != nil {
				return err
			}
		}
		msg.Text = "Successfully approved"
		_, err = c.Bot.Send(msg)
		return err
	}

	// auth guard
	if !c.CheckAuth(meta.from) {
		msg.Text = "Non-authorized action, type /start to login"
		_, err := c.Bot.Send(msg)
		return err
	}

	switch meta.typ {
	case "callback":
		if err := c.processCallback(update.CallbackQuery); err != nil {
			return err
		}

		cbcfg := tgbotapi.NewCallback(update.CallbackQuery.ID, "")
		_, err := c.Bot.AnswerCallbackQuery(cbcfg)
		return err
	case "message":
		return c.processMessage(update.Message)
	}

	return nil
}

// processCallback analyzes observables with a selected set of analyzers
func (c *Cortexbot) processCallback(callback *tgbotapi.CallbackQuery) error {
	var j cortex.Observable
	var err error

	if callback.Message.ReplyToMessage.Document != nil {
		link, err := c.Bot.GetFileDirectURL(callback.Message.ReplyToMessage.Document.FileID)
		if err != nil {
			log.Println("Can't get direct link to file")
			return err
		}

		j, err = newFileArtifactFromURL(link, callback.Message.ReplyToMessage.Document.FileName, c.TLP, c.PAP, c.Bot.Client)
		if err != nil {
			log.Println(err.Error())
			return err
		}
	} else {
		j = newArtifact(callback.Message.ReplyToMessage.Text, c.TLP, c.PAP)
	}

	switch callback.Data {
	case "all":
		mul := c.Cortex.Analyzers.NewMultiRun(context.Background(), c.Timeout)
		mul.OnReport = func(r *cortex.Report) {
			err = c.sendReport(r, callback)
			log.Println(err)
		}

		mul.OnError = func(e error, o cortex.Observable, a *cortex.Analyzer) {
			log.Println(fmt.Sprintf("Cortex analyzer %s failed on data %s with an error: %s", a.Name, o.Description(), e.Error()))
		}

		err = mul.Do(j)
		if err != nil {
			return err
		}
	case "close":
		kb := showButton()
		edit := tgbotapi.NewEditMessageReplyMarkup(callback.Message.Chat.ID, callback.Message.MessageID, *kb)
		if _, err := c.Bot.Send(edit); err != nil {
			return err
		}
	case "show":
		kb, err := c.analyzersButtons(dataType(callback.Message.ReplyToMessage.Text))
		if err != nil {
			return err
		}
		edit := tgbotapi.NewEditMessageReplyMarkup(callback.Message.Chat.ID, callback.Message.MessageID, *kb)
		if _, err := c.Bot.Send(edit); err != nil {
			return err
		}
	default:
		r, err := c.Cortex.Analyzers.Run(context.Background(), callback.Data, j, c.Timeout)
		if err != nil {
			msg := tgbotapi.NewMessage(callback.Message.Chat.ID, fmt.Sprintf("%s failed: %s", callback.Data, err.Error()))
			msg.ReplyToMessageID = callback.Message.MessageID
			if _, err := c.Bot.Send(msg); err != nil {
				return err
			}
		} else {
			err = c.sendReport(r, callback)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// analyzersButtons returns a markup of analyzers as buttons
func (c *Cortexbot) analyzersButtons(datatype string) (*tgbotapi.InlineKeyboardMarkup, error) {
	analyzers, _, err := c.Cortex.Analyzers.ListByType(context.Background(), datatype)
	if err != nil {
		return nil, err
	}

	var names []string
	for i := range analyzers {
		names = append(names, analyzers[i].Name)
	}
	sort.Strings(names)

	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("All", "all"),
	})

	for _, n := range names {
		var buttons []tgbotapi.InlineKeyboardButton
		b := tgbotapi.NewInlineKeyboardButtonData(n, n)
		buttons = append(buttons, b)
		rows = append(rows, buttons)
	}

	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("Close", "close"),
	})

	markup := tgbotapi.NewInlineKeyboardMarkup(rows...)
	return &markup, nil
}

// showButton returns only one button, used on close callback
func showButton() *tgbotapi.InlineKeyboardMarkup {
	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("Show", "show"),
	})

	markup := tgbotapi.NewInlineKeyboardMarkup(rows...)
	return &markup
}

// processMessage asks Cortex about data submitted by a user
func (c *Cortexbot) processMessage(input *tgbotapi.Message) error {
	msg := tgbotapi.NewMessage(input.Chat.ID, "Select analyzer to run. Choose <All> to run all of them.")
	msg.ReplyToMessageID = input.MessageID

	var dt string

	if input.Document != nil {
		dt = "file"
	} else {
		dt = dataType(input.Text)
	}

	bmarkup, err := c.analyzersButtons(dt)
	if err != nil {
		return err
	}
	msg.ReplyMarkup = bmarkup

	_, err = c.Bot.Send(msg)
	if err != nil {
		return err
	}

	return nil
}

// buildTaxonomies joins taxonomies in one formatted string
// Every taxonomy is separated with two spaces from each other
func buildTaxonomies(txs []cortex.Taxonomy) string {
	var stats []string

	for _, t := range txs {
		stats = append(stats, fmt.Sprintf("%s:%s = %v", t.Namespace, t.Predicate, t.Value))
	}
	return strings.Join(stats, "\n")
}

// newArtifact makes an Artifact depends on its type
func newArtifact(s string, tlp cortex.TLP, pap cortex.PAP) cortex.Observable {
	return &cortex.Task{
		Data:     s,
		DataType: dataType(s),
		TLP:      &tlp,
		PAP:      &pap,
	}
}

func dataType(d string) (t string) {
	if valid.IsIP(d) {
		t = "ip"
	} else if IsDNSName(d) {
		t = "domain"
	} else if IsHash(d) {
		t = "hash"
	} else if valid.IsEmail(d) {
		t = "mail"
	} else if valid.IsURL(d) {
		t = "url"
	} else {
		t = "other"
	}

	return t
}

// newFileArtifactFromURL makes a FileArtifact from URL
func newFileArtifactFromURL(link, fname string, tlp cortex.TLP, pap cortex.PAP, client *http.Client) (cortex.Observable, error) {
	req, err := http.NewRequest("GET", link, nil)
	if err != nil {
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	return &cortex.FileTask{
		FileTaskMeta: cortex.FileTaskMeta{
			DataType: "file",
			TLP:      &tlp,
			PAP:      &pap,
		},
		FileName: fname,
		Reader:   resp.Body,
	}, nil
}
<|endoftext|>


<commit_before>package fileproxy

import (
	"bytes"
	"path"
)

func process(params *Params, filePath string, file *bytes.Buffer) (*bytes.Buffer, error) {
	if params.IsDefault {
		return file, nil
	}

	gm := NewGMBuilder()
	gm.Strip()

	if params.EnableResize {
		switch {
		case params.Width > 1 && params.Height > 1:
			gm.Resize(params.Width, params.Height, "!")
		case params.Width < 1 && params.Height > 1:
			gm.Resize(1, params.Height, "^")
		case params.Width > 1 && params.Height < 1:
			gm.Resize(params.Width, 1, "^")
		}
	}

	if params.EnableGravity {
		gm.Gravity(getGravity(params.Gravity))
	}

	if params.EnableCrop {
		gm.Crop(params.CropWidth, params.CropHeight)
	}

	if params.Quality != -1 {
		gm.Quality(params.Quality)
	}

	out := &bytes.Buffer{}
	err := gmProcess(file, out, gm)

	return out, err
}

func getGravity(cropPos string) string {
	switch cropPos {
	case "nw":
		return "NorthWest"
	case "n":
		return "North"
	case "ne":
		return "NorthEast"
	case "w":
		return "West"
	case "c":
		return "Center"
	case "e":
		return "East"
	case "sw":
		return "SouthWest"
	case "s":
		return "South"
	case "se":
		return "SouthEast"
	}

	return "NorthWest"
}
<commit_msg>fix bug<commit_after>package fileproxy

import (
	"bytes"
)

func process(params *Params, filePath string, file *bytes.Buffer) (*bytes.Buffer, error) {
	if params.IsDefault {
		return file, nil
	}

	gm := NewGMBuilder()
	gm.Strip()

	if params.EnableResize {
		switch {
		case params.Width > 1 && params.Height > 1:
			gm.Resize(params.Width, params.Height, "!")
		case params.Width < 1 && params.Height > 1:
			gm.Resize(1, params.Height, "^")
		case params.Width > 1 && params.Height < 1:
			gm.Resize(params.Width, 1, "^")
		}
	}

	if params.EnableGravity {
		gm.Gravity(getGravity(params.Gravity))
	}

	if params.EnableCrop {
		gm.Crop(params.CropWidth, params.CropHeight)
	}

	if params.Quality != -1 {
		gm.Quality(params.Quality)
	}

	out := &bytes.Buffer{}
	err := gmProcess(file, out, gm)

	return out, err
}

func getGravity(cropPos string) string {
	switch cropPos {
	case "nw":
		return "NorthWest"
	case "n":
		return "North"
	case "ne":
		return "NorthEast"
	case "w":
		return "West"
	case "c":
		return "Center"
	case "e":
		return "East"
	case "sw":
		return "SouthWest"
	case "s":
		return "South"
	case "se":
		return "SouthEast"
	}

	return "NorthWest"
}
<|endoftext|>