mirror of
https://github.com/zyedidia/micro.git
synced 2026-02-05 14:40:20 +09:00
Start plugin support and plugin manager
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -28,10 +29,10 @@ func init() {
|
||||
}
|
||||
|
||||
// LoadFile loads a lua file
|
||||
func LoadFile(module string, file string, data string) error {
|
||||
pluginDef := "local P = {};" + module + " = P;setmetatable(" + module + ", {__index = _G});setfenv(1, P);"
|
||||
func LoadFile(module string, file string, data []byte) error {
|
||||
pluginDef := []byte("module(\"" + module + "\", package.seeall)")
|
||||
|
||||
if fn, err := L.Load(strings.NewReader(pluginDef+data), file); err != nil {
|
||||
if fn, err := L.Load(bytes.NewReader(append(pluginDef, data...)), file); err != nil {
|
||||
return err
|
||||
} else {
|
||||
L.Push(fn)
|
||||
|
||||
67
cmd/micro/lua/plugin.go
Normal file
67
cmd/micro/lua/plugin.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
var ErrNoSuchFunction = errors.New("No such function exists")
|
||||
|
||||
type Plugin struct {
|
||||
name string
|
||||
files []string
|
||||
}
|
||||
|
||||
func NewPluginFromDir(name string, dir string) (*Plugin, error) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := new(Plugin)
|
||||
p.name = name
|
||||
|
||||
for _, f := range files {
|
||||
if strings.HasSuffix(f.Name(), ".lua") {
|
||||
p.files = append(p.files, dir+f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Plugin) Load() error {
|
||||
for _, f := range p.files {
|
||||
dat, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = LoadFile(p.name, f, dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||
plug := L.GetGlobal(p.name)
|
||||
luafn := L.GetField(plug, fn)
|
||||
if luafn == lua.LNil {
|
||||
return nil, ErrNoSuchFunction
|
||||
}
|
||||
err := L.CallByParam(lua.P{
|
||||
Fn: luafn,
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := L.Get(-1)
|
||||
L.Pop(1)
|
||||
return ret, nil
|
||||
}
|
||||
46
cmd/micro/manager/fetch.go
Normal file
46
cmd/micro/manager/fetch.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/config"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
)
|
||||
|
||||
// NewPluginInfoFromUrl creates a new PluginInfo from a URL by fetching
|
||||
// the data at that URL and parsing the JSON (running a GET request at
|
||||
// the URL should return the JSON for a plugin info)
|
||||
func NewPluginInfoFromUrl(url string) (*PluginInfo, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
dat, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewPluginInfo(dat)
|
||||
}
|
||||
|
||||
// FetchRepo downloads this plugin's git repository
|
||||
func (i *PluginInfo) FetchRepo() error {
|
||||
_, err := git.PlainClone(path.Join(config.ConfigDir, "plugin", i.Name), false, &git.CloneOptions{
|
||||
URL: i.Repo,
|
||||
Progress: nil,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *PluginInfo) FetchDeps() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *PluginInfo) PostInstallHooks() error {
|
||||
return nil
|
||||
}
|
||||
72
cmd/micro/manager/manager_test.go
Normal file
72
cmd/micro/manager/manager_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config.InitConfigDir("./")
|
||||
}
|
||||
|
||||
var sampleJson = []byte(`{
|
||||
"name": "comment",
|
||||
"description": "Plugin to auto comment or uncomment lines",
|
||||
"website": "https://github.com/micro-editor/comment-plugin",
|
||||
"repository": "https://github.com/micro-editor/comment-plugin",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.6",
|
||||
"tag": "v1.0.6",
|
||||
"require": {
|
||||
"micro": ">=1.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "1.0.5",
|
||||
"tag": "v1.0.5",
|
||||
"require": {
|
||||
"micro": ">=1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "1.0.6-dev",
|
||||
"tag": "nightly",
|
||||
"require": {
|
||||
"micro": ">=1.3.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
_, err := NewPluginInfo(sampleJson)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// func TestFetch(t *testing.T) {
|
||||
// i, err := NewPluginInfoFromUrl("http://zbyedidia.webfactional.com/micro/test.json")
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
//
|
||||
// err = i.FetchRepo()
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
is, err := ListInstalledPlugins()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, i := range is {
|
||||
fmt.Println(i.Name)
|
||||
}
|
||||
}
|
||||
149
cmd/micro/manager/parser.go
Normal file
149
cmd/micro/manager/parser.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/zyedidia/micro/cmd/micro/config"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingName = errors.New("Missing or empty name field")
|
||||
ErrMissingDesc = errors.New("Missing or empty description field")
|
||||
ErrMissingSite = errors.New("Missing or empty website field")
|
||||
ErrMissingRepo = errors.New("Missing or empty repository field")
|
||||
ErrMissingVersions = errors.New("Missing or empty versions field")
|
||||
ErrMissingTag = errors.New("Missing or empty tag field")
|
||||
ErrMissingRequire = errors.New("Missing or empty require field")
|
||||
)
|
||||
|
||||
type Plugin struct {
|
||||
info *PluginInfo
|
||||
dir string
|
||||
repo *git.Repository
|
||||
// Index into info.Versions showing the current version of this plugin
|
||||
Version int
|
||||
}
|
||||
|
||||
// PluginVersion describes a version for a plugin as well as any dependencies that
|
||||
// version might have
|
||||
// This marks a tag that corresponds to the version in the git repo
|
||||
type PluginVersion struct {
|
||||
Vers semver.Version
|
||||
Vstr string `json:"version"`
|
||||
Tag string `json:"tag"`
|
||||
Require map[string]string `json:"require"`
|
||||
}
|
||||
|
||||
// PluginInfo contains all the needed info about a plugin
|
||||
type PluginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"description"`
|
||||
Site string `json:"website"`
|
||||
Repo string `json:"repository"`
|
||||
Versions []PluginVersion `json:"versions"`
|
||||
}
|
||||
|
||||
// NewPluginInfo parses a JSON input into a valid PluginInfo struct
|
||||
// Returns an error if there are any missing fields or any invalid fields
|
||||
// There are no optional fields in a plugin info json file
|
||||
func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
||||
var info PluginInfo
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
dec.DisallowUnknownFields() // Force errors
|
||||
|
||||
if err := dec.Decode(&info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(info.Name) == 0 {
|
||||
return nil, ErrMissingName
|
||||
} else if len(info.Desc) == 0 {
|
||||
return nil, ErrMissingDesc
|
||||
} else if len(info.Site) == 0 {
|
||||
return nil, ErrMissingSite
|
||||
} else if len(info.Repo) == 0 {
|
||||
return nil, ErrMissingRepo
|
||||
} else if err := info.makeVersions(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (i *PluginInfo) makeVersions() error {
|
||||
if len(i.Versions) == 0 {
|
||||
return ErrMissingVersions
|
||||
}
|
||||
|
||||
for _, v := range i.Versions {
|
||||
sv, err := semver.Make(v.Vstr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Vers = sv
|
||||
if len(v.Tag) == 0 {
|
||||
return ErrMissingTag
|
||||
} else if v.Require == nil {
|
||||
return ErrMissingRequire
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstalledPlugins searches the config directory for all installed plugins
|
||||
// and returns the list of plugin infos corresponding to them
|
||||
func ListInstalledPlugins() ([]*Plugin, error) {
|
||||
pdir := path.Join(config.ConfigDir, "plugin")
|
||||
|
||||
files, err := ioutil.ReadDir(pdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var plugins []*Plugin
|
||||
|
||||
for _, dir := range files {
|
||||
if dir.IsDir() {
|
||||
files, err := ioutil.ReadDir(path.Join(pdir, dir.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if f.Name() == "repo.json" {
|
||||
dat, err := ioutil.ReadFile(path.Join(pdir, dir.Name(), "repo.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := NewPluginInfo(dat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dirname := path.Join(pdir, dir.Name())
|
||||
r, err := git.PlainOpen(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &Plugin{
|
||||
info: info,
|
||||
dir: dirname,
|
||||
repo: r,
|
||||
}
|
||||
|
||||
plugins = append(plugins, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return plugins, nil
|
||||
}
|
||||
Reference in New Issue
Block a user