]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/pluginmanager.go
Merge pull request #507 from NicolaiSoeborg/master
[micro.git] / cmd / micro / pluginmanager.go
index 0c4fbef2dc178b795f35fe7f07a05fd5bbd30e93..0703a62f5d1d4339aeceb35728cf5281a50a1eb3 100644 (file)
@@ -14,16 +14,12 @@ import (
        "sync"
 
        "github.com/blang/semver"
-       "github.com/yosuke-furukawa/json5/encoding/json5"
        "github.com/yuin/gopher-lua"
+       "github.com/zyedidia/json5/encoding/json5"
 )
 
 var (
-       pluginChannels PluginChannels = PluginChannels{
-               PluginChannel("https://www.boombuler.de/channel.json"),
-       }
-
-       allPluginPackages PluginPackages = nil
+       allPluginPackages PluginPackages
 )
 
 // CorePluginName is a plugin dependency name for the micro core.
@@ -61,7 +57,7 @@ type PluginVersion struct {
 // PluginVersions is a slice of PluginVersion
 type PluginVersions []*PluginVersion
 
-// PluginDenendency descripes a dependency to another plugin or micro itself.
+// PluginDependency descripes a dependency to another plugin or micro itself.
 type PluginDependency struct {
        Name  string
        Range semver.Range
@@ -81,6 +77,7 @@ func (pp *PluginPackage) String() string {
                buf.WriteRune('\n')
        }
        if pp.Description != "" {
+               buf.WriteRune('\n')
                buf.WriteString(pp.Description)
        }
        return buf.String()
@@ -123,7 +120,7 @@ func (pc PluginChannels) Fetch() PluginPackages {
 
 // Fetch retrieves all available PluginPackages from the given channel
 func (pc PluginChannel) Fetch() PluginPackages {
-       messenger.AddLog(fmt.Sprintf("Fetching channel: %q", string(pc)))
+       // messenger.AddLog(fmt.Sprintf("Fetching channel: %q", string(pc)))
        resp, err := http.Get(string(pc))
        if err != nil {
                TermMessage("Failed to query plugin channel:\n", err)
@@ -144,7 +141,7 @@ func (pc PluginChannel) Fetch() PluginPackages {
 
 // Fetch retrieves all available PluginPackages from the given repository
 func (pr PluginRepository) Fetch() PluginPackages {
-       messenger.AddLog(fmt.Sprintf("Fetching repository: %q", string(pr)))
+       // messenger.AddLog(fmt.Sprintf("Fetching repository: %q", string(pr)))
        resp, err := http.Get(string(pr))
        if err != nil {
                TermMessage("Failed to query plugin repository:\n", err)
@@ -158,7 +155,11 @@ func (pr PluginRepository) Fetch() PluginPackages {
                TermMessage("Failed to decode repository data:\n", err)
                return PluginPackages{}
        }
-       return plugins
+       if len(plugins) > 0 {
+               return PluginPackages{plugins[0]}
+       }
+       return nil
+       // return plugins
 }
 
 // UnmarshalJSON unmarshals raw json to a PluginVersion
@@ -177,8 +178,13 @@ func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
        pv.Require = make(PluginDependencies, 0)
 
        for k, v := range values.Require {
-               if vRange, err := semver.ParseRange(v); err == nil {
-                       pv.Require = append(pv.Require, &PluginDependency{k, vRange})
+               // don't add the dependency if it's the core and
+               // we have a unknown version number.
+               // in that case just accept that dependency (which equals to not adding it.)
+               if k != CorePluginName || !isUnknownCoreVersion() {
+                       if vRange, err := semver.ParseRange(v); err == nil {
+                               pv.Require = append(pv.Require, &PluginDependency{k, vRange})
+                       }
                }
        }
        return nil
@@ -210,7 +216,39 @@ func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
 // GetAllPluginPackages gets all PluginPackages which may be available.
 func GetAllPluginPackages() PluginPackages {
        if allPluginPackages == nil {
-               allPluginPackages = pluginChannels.Fetch()
+               getOption := func(name string) []string {
+                       data := GetOption(name)
+                       if strs, ok := data.([]string); ok {
+                               return strs
+                       }
+                       if ifs, ok := data.([]interface{}); ok {
+                               result := make([]string, len(ifs))
+                               for i, urlIf := range ifs {
+                                       if url, ok := urlIf.(string); ok {
+                                               result[i] = url
+                                       } else {
+                                               return nil
+                                       }
+                               }
+                               return result
+                       }
+                       return nil
+               }
+
+               channels := PluginChannels{}
+               for _, url := range getOption("pluginchannels") {
+                       channels = append(channels, PluginChannel(url))
+               }
+               repos := []PluginRepository{}
+               for _, url := range getOption("pluginrepos") {
+                       repos = append(repos, PluginRepository(url))
+               }
+               allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
+                       if i == 0 {
+                               return channels.Fetch()
+                       }
+                       return repos[i-1].Fetch()
+               })
        }
        return allPluginPackages
 }
@@ -235,8 +273,8 @@ func (pv PluginVersions) Swap(i, j int) {
 }
 
 // Less returns true if the version at position i is greater then the version at position j (used for sorting)
-func (s PluginVersions) Less(i, j int) bool {
-       return s[i].Version.GT(s[j].Version)
+func (pv PluginVersions) Less(i, j int) bool {
+       return pv[i].Version.GT(pv[j].Version)
 }
 
 // Match returns true if the package matches a given search text
@@ -259,27 +297,40 @@ func (pp PluginPackage) Match(text string) bool {
 }
 
 // IsInstallable returns true if the package can be installed.
-func (pp PluginPackage) IsInstallable() bool {
-       _, err := GetAllPluginPackages().Resolve(GetInstalledVersions(), PluginDependencies{
+func (pp PluginPackage) IsInstallable() error {
+       _, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
                &PluginDependency{
                        Name:  pp.Name,
                        Range: semver.Range(func(v semver.Version) bool { return true }),
                }})
-       return err == nil
+       return err
 }
 
 // SearchPlugin retrieves a list of all PluginPackages which match the given search text and
 // could be or are already installed
-func SearchPlugin(text string) (plugins PluginPackages) {
+func SearchPlugin(texts []string) (plugins PluginPackages) {
        plugins = make(PluginPackages, 0)
+
+pluginLoop:
        for _, pp := range GetAllPluginPackages() {
-               if pp.Match(text) && pp.IsInstallable() {
+               for _, text := range texts {
+                       if !pp.Match(text) {
+                               continue pluginLoop
+                       }
+               }
+
+               if err := pp.IsInstallable(); err == nil {
                        plugins = append(plugins, pp)
                }
        }
        return
 }
 
+func isUnknownCoreVersion() bool {
+       _, err := semver.ParseTolerant(Version)
+       return err != nil
+}
+
 func newStaticPluginVersion(name, version string) *PluginVersion {
        vers, err := semver.ParseTolerant(version)
 
@@ -301,13 +352,14 @@ func newStaticPluginVersion(name, version string) *PluginVersion {
 
 // GetInstalledVersions returns a list of all currently installed plugins including an entry for
 // micro itself. This can be used to resolve dependencies.
-func GetInstalledVersions() PluginVersions {
-       result := PluginVersions{
-               newStaticPluginVersion(CorePluginName, Version),
+func GetInstalledVersions(withCore bool) PluginVersions {
+       result := PluginVersions{}
+       if withCore {
+               result = append(result, newStaticPluginVersion(CorePluginName, Version))
        }
 
-       for _, name := range loadedPlugins {
-               version := GetInstalledPluginVersion(name)
+       for name, lpname := range loadedPlugins {
+               version := GetInstalledPluginVersion(lpname)
                if pv := newStaticPluginVersion(name, version); pv != nil {
                        result = append(result, pv)
                }
@@ -329,6 +381,7 @@ func GetInstalledPluginVersion(name string) string {
        return ""
 }
 
+// DownloadAndInstall downloads and installs the given plugin and version
 func (pv *PluginVersion) DownloadAndInstall() error {
        messenger.AddLog(fmt.Sprintf("Downloading %q (%s) from %q", pv.pack.Name, pv.Version, pv.Url))
        resp, err := http.Get(pv.Url)
@@ -350,8 +403,32 @@ func (pv *PluginVersion) DownloadAndInstall() error {
        if err = os.MkdirAll(targetDir, dirPerm); err != nil {
                return err
        }
+
+       // Check if all files in zip are in the same directory.
+       // this might be the case if the plugin zip contains the whole plugin dir
+       // instead of its content.
+       var prefix string
+       allPrefixed := false
+       for i, f := range z.File {
+               parts := strings.Split(f.Name, "/")
+               if i == 0 {
+                       prefix = parts[0]
+               } else if parts[0] != prefix {
+                       allPrefixed = false
+                       break
+               } else {
+                       // switch to true since we have at least a second file
+                       allPrefixed = true
+               }
+       }
+
        for _, f := range z.File {
-               targetName := filepath.Join(targetDir, filepath.Join(strings.Split(f.Name, "/")...))
+               parts := strings.Split(f.Name, "/")
+               if allPrefixed {
+                       parts = parts[1:]
+               }
+
+               targetName := filepath.Join(targetDir, filepath.Join(parts...))
                if f.FileInfo().IsDir() {
                        if err := os.MkdirAll(targetName, dirPerm); err != nil {
                                return err
@@ -362,13 +439,13 @@ func (pv *PluginVersion) DownloadAndInstall() error {
                                return err
                        }
                        defer content.Close()
-                       if target, err := os.Create(targetName); err != nil {
+                       target, err := os.Create(targetName)
+                       if err != nil {
+                               return err
+                       }
+                       defer target.Close()
+                       if _, err = io.Copy(target, content); err != nil {
                                return err
-                       } else {
-                               defer target.Close()
-                               if _, err = io.Copy(target, content); err != nil {
-                                       return err
-                               }
                        }
                }
        }
@@ -418,6 +495,7 @@ func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies
        return result
 }
 
+// Resolve resolves dependencies between different plugins
 func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
        if len(open) == 0 {
                return selectedVersions, nil
@@ -429,31 +507,29 @@ func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDe
                                return all.Resolve(selectedVersions, stillOpen)
                        }
                        return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
-               } else {
-                       availableVersions := all.GetAllVersions(currentRequirement.Name)
-                       sort.Sort(availableVersions)
+               }
+               availableVersions := all.GetAllVersions(currentRequirement.Name)
+               sort.Sort(availableVersions)
 
-                       for _, version := range availableVersions {
-                               if currentRequirement.Range(version.Version) {
-                                       resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
+               for _, version := range availableVersions {
+                       if currentRequirement.Range(version.Version) {
+                               resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
 
-                                       if err == nil {
-                                               return resolved, nil
-                                       }
+                               if err == nil {
+                                       return resolved, nil
                                }
                        }
-                       return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
                }
-       } else {
-               return selectedVersions, nil
+               return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
        }
+       return selectedVersions, nil
 }
 
-func (versions PluginVersions) install() {
+func (pv PluginVersions) install() {
        anyInstalled := false
-       currentlyInstalled := GetInstalledVersions()
+       currentlyInstalled := GetInstalledVersions(true)
 
-       for _, sel := range versions {
+       for _, sel := range pv {
                if sel.pack.Name != CorePluginName {
                        shouldInstall := true
                        if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
@@ -485,11 +561,14 @@ func (versions PluginVersions) install() {
 func UninstallPlugin(name string) {
        if err := os.RemoveAll(filepath.Join(configDir, "plugins", name)); err != nil {
                messenger.Error(err)
+               return
        }
+       delete(loadedPlugins, name)
 }
 
+// Install installs the plugin
 func (pl PluginPackage) Install() {
-       selected, err := GetAllPluginPackages().Resolve(GetInstalledVersions(), PluginDependencies{
+       selected, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
                &PluginDependency{
                        Name:  pl.Name,
                        Range: semver.Range(func(v semver.Version) bool { return true }),
@@ -501,14 +580,22 @@ func (pl PluginPackage) Install() {
        selected.install()
 }
 
-func UpdatePlugins() {
+// UpdatePlugins updates the given plugins
+func UpdatePlugins(plugins []string) {
+       // if no plugins are specified, update all installed plugins.
+       if len(plugins) == 0 {
+               for name := range loadedPlugins {
+                       plugins = append(plugins, name)
+               }
+       }
+
        messenger.AddLog("Checking for plugin updates")
        microVersion := PluginVersions{
                newStaticPluginVersion(CorePluginName, Version),
        }
 
        var updates = make(PluginDependencies, 0)
-       for _, name := range loadedPlugins {
+       for _, name := range plugins {
                pv := GetInstalledPluginVersion(name)
                r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
                if err == nil {