Initial commit

This commit is contained in:
Stash Dev
2019-02-09 04:30:49 -08:00
commit 87eeed7e71
1093 changed files with 558731 additions and 0 deletions

21
vendor/github.com/gobuffalo/buffalo-plugins/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright © 2018 Mark Bates
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,96 @@
package plugins
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"os"
"os/user"
"path/filepath"
"sync"
"github.com/gobuffalo/envy"
)
type cachedPlugin struct {
Commands Commands `json:"commands"`
CheckSum string `json:"check_sum"`
}
type cachedPlugins map[string]cachedPlugin
var cachePath = func() string {
home := "."
if usr, err := user.Current(); err == nil {
home = usr.HomeDir
}
return filepath.Join(home, ".buffalo", "plugin.cache")
}()
var cacheMoot sync.RWMutex
var cacheOn = envy.Get("BUFFALO_PLUGIN_CACHE", "on")
var cache = func() cachedPlugins {
m := cachedPlugins{}
if cacheOn != "on" {
return m
}
f, err := os.Open(cachePath)
if err != nil {
return m
}
defer f.Close()
if err := json.NewDecoder(f).Decode(&m); err != nil {
f.Close()
os.Remove(f.Name())
}
return m
}()
func findInCache(path string) (cachedPlugin, bool) {
cacheMoot.RLock()
defer cacheMoot.RUnlock()
cp, ok := cache[path]
return cp, ok
}
func saveCache() error {
if cacheOn != "on" {
return nil
}
cacheMoot.Lock()
defer cacheMoot.Unlock()
os.MkdirAll(filepath.Dir(cachePath), 0744)
f, err := os.Create(cachePath)
if err != nil {
return err
}
return json.NewEncoder(f).Encode(cache)
}
func sum(path string) string {
f, err := os.Open(path)
if err != nil {
return ""
}
defer f.Close()
hash := sha256.New()
if _, err := io.Copy(hash, f); err != nil {
return ""
}
sum := hash.Sum(nil)
s := fmt.Sprintf("%x", sum)
return s
}
func addToCache(path string, cp cachedPlugin) {
if cp.CheckSum == "" {
cp.CheckSum = sum(path)
}
cacheMoot.Lock()
defer cacheMoot.Unlock()
cache[path] = cp
}

View File

@@ -0,0 +1,19 @@
package plugins
// Command that the plugin supplies
type Command struct {
// Name "foo"
Name string `json:"name"`
// UseCommand "bar"
UseCommand string `json:"use_command"`
// BuffaloCommand "generate"
BuffaloCommand string `json:"buffalo_command"`
// Description "generates a foo"
Description string `json:"description,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Binary string `json:"-"`
Flags []string `json:"flags,omitempty"`
}
// Commands is a slice of Command
type Commands []Command

View File

@@ -0,0 +1,98 @@
package plugins
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/gobuffalo/envy"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// ErrPlugMissing ...
var ErrPlugMissing = errors.New("plugin missing")
func Decorate(c Command) *cobra.Command {
var flags []string
if len(c.Flags) > 0 {
for _, f := range c.Flags {
flags = append(flags, f)
}
}
cc := &cobra.Command{
Use: c.Name,
Short: fmt.Sprintf("[PLUGIN] %s", c.Description),
Aliases: c.Aliases,
RunE: func(cmd *cobra.Command, args []string) error {
plugCmd := c.Name
if c.UseCommand != "" {
plugCmd = c.UseCommand
}
ax := []string{plugCmd}
if plugCmd == "-" {
ax = []string{}
}
ax = append(ax, args...)
ax = append(ax, flags...)
bin, err := LookPath(c.Binary)
if err != nil {
return errors.WithStack(err)
}
ex := exec.Command(bin, ax...)
if runtime.GOOS != "windows" {
ex.Env = append(envy.Environ(), "BUFFALO_PLUGIN=1")
}
ex.Stdin = os.Stdin
ex.Stdout = os.Stdout
ex.Stderr = os.Stderr
return log(strings.Join(ex.Args, " "), ex.Run)
},
}
cc.DisableFlagParsing = true
return cc
}
// LookPath ...
func LookPath(s string) (string, error) {
if _, err := os.Stat(s); err == nil {
return s, nil
}
if lp, err := exec.LookPath(s); err == nil {
return lp, err
}
var bin string
pwd, err := os.Getwd()
if err != nil {
return "", errors.WithStack(err)
}
var looks []string
if from, err := envy.MustGet("BUFFALO_PLUGIN_PATH"); err == nil {
looks = append(looks, from)
} else {
looks = []string{filepath.Join(pwd, "plugins"), filepath.Join(envy.GoPath(), "bin"), envy.Get("PATH", "")}
}
for _, p := range looks {
lp := filepath.Join(p, s)
if lp, err = filepath.EvalSymlinks(lp); err == nil {
bin = lp
break
}
}
if len(bin) == 0 {
return "", errors.Wrapf(ErrPlugMissing, "could not find %s in %q", s, looks)
}
return bin, nil
}

View File

@@ -0,0 +1,7 @@
package plugins
const (
EvtSetupStarted = "buffalo-plugins:setup:started"
EvtSetupErr = "buffalo-plugins:setup:err"
EvtSetupFinished = "buffalo-plugins:setup:finished"
)

View File

@@ -0,0 +1,7 @@
//+build !debug
package plugins
func log(_ string, fn func() error) error {
return fn()
}

View File

@@ -0,0 +1,14 @@
//+build debug
package plugins
import (
"fmt"
"time"
)
func log(name string, fn func() error) error {
start := time.Now()
defer fmt.Println(name, time.Now().Sub(start))
return fn()
}

View File

@@ -0,0 +1,16 @@
package plugdeps
import "encoding/json"
// Command is the plugin command you want to control further
type Command struct {
Name string `toml:"name" json:"name"`
Flags []string `toml:"flags,omitempty" json:"flags,omitempty"`
Commands []Command `toml:"command,omitempty" json:"commands,omitempty"`
}
// String implementation of fmt.Stringer
func (p Command) String() string {
b, _ := json.Marshal(p)
return string(b)
}

View File

@@ -0,0 +1,88 @@
package plugdeps
import (
"os"
"path/filepath"
"strings"
"github.com/gobuffalo/meta"
"github.com/karrick/godirwalk"
"github.com/pkg/errors"
)
// ErrMissingConfig is if config/buffalo-plugins.toml file is not found. Use plugdeps#On(app) to test if plugdeps are being used
var ErrMissingConfig = errors.Errorf("could not find a buffalo-plugins config file at %s", ConfigPath(meta.New(".")))
// List all of the plugins the application depeneds on. Will return ErrMissingConfig
// if the app is not using config/buffalo-plugins.toml to manage their plugins.
// Use plugdeps#On(app) to test if plugdeps are being used.
func List(app meta.App) (*Plugins, error) {
plugs := New()
if app.WithPop {
plugs.Add(pop)
}
lp, err := listLocal(app)
if err != nil {
return plugs, errors.WithStack(err)
}
plugs.Add(lp.List()...)
if !On(app) {
return plugs, ErrMissingConfig
}
p := ConfigPath(app)
tf, err := os.Open(p)
if err != nil {
return plugs, errors.WithStack(err)
}
if err := plugs.Decode(tf); err != nil {
return plugs, errors.WithStack(err)
}
return plugs, nil
}
func listLocal(app meta.App) (*Plugins, error) {
plugs := New()
proot := filepath.Join(app.Root, "plugins")
if _, err := os.Stat(proot); err != nil {
return plugs, nil
}
err := godirwalk.Walk(proot, &godirwalk.Options{
FollowSymbolicLinks: true,
Callback: func(path string, info *godirwalk.Dirent) error {
if info.IsDir() {
return nil
}
base := filepath.Base(path)
if strings.HasPrefix(base, "buffalo-") {
plugs.Add(Plugin{
Binary: base,
Local: "." + strings.TrimPrefix(path, app.Root),
})
}
return nil
},
})
if err != nil {
return plugs, errors.WithStack(err)
}
return plugs, nil
}
// ConfigPath returns the path to the config/buffalo-plugins.toml file
// relative to the app
func ConfigPath(app meta.App) string {
return filepath.Join(app.Root, "config", "buffalo-plugins.toml")
}
// On checks for the existence of config/buffalo-plugins.toml if this
// file exists its contents will be used to list plugins. If the file is not
// found, then the BUFFALO_PLUGIN_PATH and ./plugins folders are consulted.
func On(app meta.App) bool {
_, err := os.Stat(ConfigPath(app))
return err == nil
}

View File

@@ -0,0 +1,26 @@
package plugdeps
import (
"encoding/json"
"github.com/gobuffalo/meta"
)
// Plugin represents a Go plugin for Buffalo applications
type Plugin struct {
Binary string `toml:"binary" json:"binary"`
GoGet string `toml:"go_get,omitempty" json:"go_get,omitempty"`
Local string `toml:"local,omitempty" json:"local,omitempty"`
Commands []Command `toml:"command,omitempty" json:"commands,omitempty"`
Tags meta.BuildTags `toml:"tags,omitempty" json:"tags,omitempty"`
}
// String implementation of fmt.Stringer
func (p Plugin) String() string {
b, _ := json.Marshal(p)
return string(b)
}
func (p Plugin) key() string {
return p.Binary + p.GoGet + p.Local
}

View File

@@ -0,0 +1,98 @@
package plugdeps
import (
"io"
"sort"
"sync"
"github.com/BurntSushi/toml"
"github.com/pkg/errors"
)
// Plugins manages the config/buffalo-plugins.toml file
// as well as the plugins available from the file.
type Plugins struct {
plugins map[string]Plugin
moot *sync.RWMutex
}
// Encode the list of plugins, in TOML format, to the reader
func (plugs *Plugins) Encode(w io.Writer) error {
tp := tomlPlugins{
Plugins: plugs.List(),
}
if err := toml.NewEncoder(w).Encode(tp); err != nil {
return errors.WithStack(err)
}
return nil
}
// Decode the list of plugins, in TOML format, from the reader
func (plugs *Plugins) Decode(r io.Reader) error {
tp := &tomlPlugins{
Plugins: []Plugin{},
}
if _, err := toml.DecodeReader(r, tp); err != nil {
return errors.WithStack(err)
}
for _, p := range tp.Plugins {
plugs.Add(p)
}
return nil
}
// List of dependent plugins listed in order of Plugin.String()
func (plugs *Plugins) List() []Plugin {
m := map[string]Plugin{}
plugs.moot.RLock()
for _, p := range plugs.plugins {
m[p.key()] = p
}
plugs.moot.RUnlock()
var pp []Plugin
for _, v := range m {
pp = append(pp, v)
}
sort.Slice(pp, func(a, b int) bool {
return pp[a].Binary < pp[b].Binary
})
return pp
}
// Add plugin(s) to the list of dependencies
func (plugs *Plugins) Add(pp ...Plugin) {
plugs.moot.Lock()
for _, p := range pp {
plugs.plugins[p.key()] = p
}
plugs.moot.Unlock()
}
// Remove plugin(s) from the list of dependencies
func (plugs *Plugins) Remove(pp ...Plugin) {
plugs.moot.Lock()
for _, p := range pp {
delete(plugs.plugins, p.key())
}
plugs.moot.Unlock()
}
// New returns a configured *Plugins value
func New() *Plugins {
plugs := &Plugins{
plugins: map[string]Plugin{},
moot: &sync.RWMutex{},
}
plugs.Add(self)
return plugs
}
type tomlPlugins struct {
Plugins []Plugin `toml:"plugin"`
}
var self = Plugin{
Binary: "buffalo-plugins",
GoGet: "github.com/gobuffalo/buffalo-plugins",
}

View File

@@ -0,0 +1,6 @@
package plugdeps
var pop = Plugin{
Binary: "buffalo-pop",
GoGet: "github.com/gobuffalo/buffalo-pop",
}

View File

@@ -0,0 +1,232 @@
package plugins
import (
"bytes"
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/gobuffalo/buffalo-plugins/plugins/plugdeps"
"github.com/gobuffalo/envy"
"github.com/gobuffalo/meta"
"github.com/karrick/godirwalk"
"github.com/markbates/oncer"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const timeoutEnv = "BUFFALO_PLUGIN_TIMEOUT"
var t = time.Second * 2
func timeout() time.Duration {
oncer.Do("plugins.timeout", func() {
rawTimeout, err := envy.MustGet(timeoutEnv)
if err == nil {
if parsed, err := time.ParseDuration(rawTimeout); err == nil {
t = parsed
} else {
logrus.Errorf("%q value is malformed assuming default %q: %v", timeoutEnv, t, err)
}
} else {
logrus.Debugf("%q not set, assuming default of %v", timeoutEnv, t)
}
})
return t
}
// List maps a Buffalo command to a slice of Command
type List map[string]Commands
var _list List
// Available plugins for the `buffalo` command.
// It will look in $GOPATH/bin and the `./plugins` directory.
// This can be changed by setting the $BUFFALO_PLUGIN_PATH
// environment variable.
//
// Requirements:
// * file/command must be executable
// * file/command must start with `buffalo-`
// * file/command must respond to `available` and return JSON of
// plugins.Commands{}
//
// Limit full path scan with direct plugin path
//
// If a file/command doesn't respond to being invoked with `available`
// within one second, buffalo will assume that it is unable to load. This
// can be changed by setting the $BUFFALO_PLUGIN_TIMEOUT environment
// variable. It must be set to a duration that `time.ParseDuration` can
// process.
func Available() (List, error) {
var err error
oncer.Do("plugins.Available", func() {
defer func() {
if err := saveCache(); err != nil {
logrus.Error(err)
}
}()
app := meta.New(".")
if plugdeps.On(app) {
_list, err = listPlugDeps(app)
return
}
paths := []string{"plugins"}
from, err := envy.MustGet("BUFFALO_PLUGIN_PATH")
if err != nil {
from, err = envy.MustGet("GOPATH")
if err != nil {
return
}
from = filepath.Join(from, "bin")
}
paths = append(paths, strings.Split(from, string(os.PathListSeparator))...)
list := List{}
for _, p := range paths {
if ignorePath(p) {
continue
}
if _, err := os.Stat(p); err != nil {
continue
}
err := godirwalk.Walk(p, &godirwalk.Options{
FollowSymbolicLinks: true,
Callback: func(path string, info *godirwalk.Dirent) error {
if err != nil {
// May indicate a permissions problem with the path, skip it
return nil
}
if info.IsDir() {
return nil
}
base := filepath.Base(path)
if strings.HasPrefix(base, "buffalo-") {
ctx, cancel := context.WithTimeout(context.Background(), timeout())
commands := askBin(ctx, path)
cancel()
for _, c := range commands {
bc := c.BuffaloCommand
if _, ok := list[bc]; !ok {
list[bc] = Commands{}
}
c.Binary = path
list[bc] = append(list[bc], c)
}
}
return nil
},
})
if err != nil {
return
}
}
_list = list
})
return _list, err
}
func askBin(ctx context.Context, path string) Commands {
start := time.Now()
defer func() {
logrus.Debugf("askBin %s=%.4f s", path, time.Since(start).Seconds())
}()
commands := Commands{}
defer func() {
addToCache(path, cachedPlugin{
Commands: commands,
})
}()
if cp, ok := findInCache(path); ok {
s := sum(path)
if s == cp.CheckSum {
logrus.Debugf("cache hit: %s", path)
commands = cp.Commands
return commands
}
}
logrus.Debugf("cache miss: %s", path)
if strings.HasPrefix(filepath.Base(path), "buffalo-no-sqlite") {
return commands
}
cmd := exec.CommandContext(ctx, path, "available")
bb := &bytes.Buffer{}
cmd.Stdout = bb
err := cmd.Run()
if err != nil {
return commands
}
msg := bb.String()
for len(msg) > 0 {
err = json.NewDecoder(strings.NewReader(msg)).Decode(&commands)
if err == nil {
return commands
}
msg = msg[1:]
}
logrus.Errorf("[PLUGIN] error decoding plugin %s: %s\n%s\n", path, err, msg)
return commands
}
func ignorePath(p string) bool {
p = strings.ToLower(p)
for _, x := range []string{`c:\windows`, `c:\program`} {
if strings.HasPrefix(p, x) {
return true
}
}
return false
}
func listPlugDeps(app meta.App) (List, error) {
list := List{}
plugs, err := plugdeps.List(app)
if err != nil {
return list, err
}
for _, p := range plugs.List() {
ctx, cancel := context.WithTimeout(context.Background(), timeout())
defer cancel()
bin := p.Binary
if len(p.Local) != 0 {
bin = p.Local
}
bin, err := LookPath(bin)
if err != nil {
if errors.Cause(err) != ErrPlugMissing {
return list, err
}
continue
}
commands := askBin(ctx, bin)
cancel()
for _, c := range commands {
bc := c.BuffaloCommand
if _, ok := list[bc]; !ok {
list[bc] = Commands{}
}
c.Binary = p.Binary
for _, pc := range p.Commands {
if c.Name == pc.Name {
c.Flags = pc.Flags
break
}
}
list[bc] = append(list[bc], c)
}
}
return list, nil
}

View File

@@ -0,0 +1,3 @@
package plugins
const Version = "v1.11.0"