--- /dev/null
+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
+}
--- /dev/null
+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
+}
--- /dev/null
+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)
+ }
+}
--- /dev/null
+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
+}