16 "github.com/blang/semver"
17 lua "github.com/yuin/gopher-lua"
18 "github.com/zyedidia/json5"
19 ulua "github.com/zyedidia/micro/v2/internal/lua"
20 "github.com/zyedidia/micro/v2/internal/util"
24 allPluginPackages PluginPackages
27 // CorePluginName is a plugin dependency name for the micro core.
28 const CorePluginName = "micro"
30 // PluginChannel contains an url to a json list of PluginRepository
31 type PluginChannel string
33 // PluginChannels is a slice of PluginChannel
34 type PluginChannels []PluginChannel
36 // PluginRepository contains an url to json file containing PluginPackages
37 type PluginRepository string
39 // PluginPackage contains the meta-data of a plugin and all available versions
40 type PluginPackage struct {
45 Versions PluginVersions
48 // PluginPackages is a list of PluginPackage instances.
49 type PluginPackages []*PluginPackage
51 // PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.
52 type PluginVersion struct {
54 Version semver.Version
56 Require PluginDependencies
59 func (pv *PluginVersion) Pack() *PluginPackage {
63 // PluginVersions is a slice of PluginVersion
64 type PluginVersions []*PluginVersion
66 // PluginDependency descripes a dependency to another plugin or micro itself.
67 type PluginDependency struct {
72 // PluginDependencies is a slice of PluginDependency
73 type PluginDependencies []*PluginDependency
75 func (pp *PluginPackage) String() string {
76 buf := new(bytes.Buffer)
77 buf.WriteString("Plugin: ")
78 buf.WriteString(pp.Name)
81 buf.WriteString("Author: ")
82 buf.WriteString(pp.Author)
85 if pp.Description != "" {
87 buf.WriteString(pp.Description)
92 func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
93 wgQuery := new(sync.WaitGroup)
96 results := make(chan PluginPackages)
98 wgDone := new(sync.WaitGroup)
100 var packages PluginPackages
101 for i := 0; i < count; i++ {
103 results <- fetcher(i)
108 packages = make(PluginPackages, 0)
109 for res := range results {
110 packages = append(packages, res...)
120 // Fetch retrieves all available PluginPackages from the given channels
121 func (pc PluginChannels) Fetch(out io.Writer) PluginPackages {
122 return fetchAllSources(len(pc), func(i int) PluginPackages {
123 return pc[i].Fetch(out)
127 // Fetch retrieves all available PluginPackages from the given channel
128 func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
129 resp, err := http.Get(string(pc))
131 fmt.Fprintln(out, "Failed to query plugin channel:\n", err)
132 return PluginPackages{}
134 defer resp.Body.Close()
135 decoder := json5.NewDecoder(resp.Body)
137 var repositories []PluginRepository
138 if err := decoder.Decode(&repositories); err != nil {
139 fmt.Fprintln(out, "Failed to decode channel data:\n", err)
140 return PluginPackages{}
142 return fetchAllSources(len(repositories), func(i int) PluginPackages {
143 return repositories[i].Fetch(out)
147 // Fetch retrieves all available PluginPackages from the given repository
148 func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
149 resp, err := http.Get(string(pr))
151 fmt.Fprintln(out, "Failed to query plugin repository:\n", err)
152 return PluginPackages{}
154 defer resp.Body.Close()
155 decoder := json5.NewDecoder(resp.Body)
157 var plugins PluginPackages
158 if err := decoder.Decode(&plugins); err != nil {
159 fmt.Fprintln(out, "Failed to decode repository data:\n", err)
160 return PluginPackages{}
162 if len(plugins) > 0 {
163 return PluginPackages{plugins[0]}
169 // UnmarshalJSON unmarshals raw json to a PluginVersion
170 func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
172 Version semver.Version
174 Require map[string]string
177 if err := json5.Unmarshal(data, &values); err != nil {
180 pv.Version = values.Version
182 pv.Require = make(PluginDependencies, 0)
184 for k, v := range values.Require {
185 // don't add the dependency if it's the core and
186 // we have a unknown version number.
187 // in that case just accept that dependency (which equals to not adding it.)
188 if k != CorePluginName || !isUnknownCoreVersion() {
189 if vRange, err := semver.ParseRange(v); err == nil {
190 pv.Require = append(pv.Require, &PluginDependency{k, vRange})
197 // UnmarshalJSON unmarshals raw json to a PluginPackage
198 func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
204 Versions PluginVersions
206 if err := json5.Unmarshal(data, &values); err != nil {
209 pp.Name = values.Name
210 pp.Description = values.Description
211 pp.Author = values.Author
212 pp.Tags = values.Tags
213 pp.Versions = values.Versions
214 for _, v := range pp.Versions {
220 // GetAllPluginPackages gets all PluginPackages which may be available.
221 func GetAllPluginPackages(out io.Writer) PluginPackages {
222 if allPluginPackages == nil {
223 getOption := func(name string) []string {
224 data := GetGlobalOption(name)
225 if strs, ok := data.([]string); ok {
228 if ifs, ok := data.([]interface{}); ok {
229 result := make([]string, len(ifs))
230 for i, urlIf := range ifs {
231 if url, ok := urlIf.(string); ok {
242 channels := PluginChannels{}
243 for _, url := range getOption("pluginchannels") {
244 channels = append(channels, PluginChannel(url))
246 repos := []PluginRepository{}
247 for _, url := range getOption("pluginrepos") {
248 repos = append(repos, PluginRepository(url))
250 allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
252 return channels.Fetch(out)
254 return repos[i-1].Fetch(out)
257 return allPluginPackages
260 func (pv PluginVersions) find(ppName string) *PluginVersion {
261 for _, v := range pv {
262 if v.pack.Name == ppName {
269 // Len returns the number of pluginversions in this slice
270 func (pv PluginVersions) Len() int {
274 // Swap two entries of the slice
275 func (pv PluginVersions) Swap(i, j int) {
276 pv[i], pv[j] = pv[j], pv[i]
279 // Less returns true if the version at position i is greater then the version at position j (used for sorting)
280 func (pv PluginVersions) Less(i, j int) bool {
281 return pv[i].Version.GT(pv[j].Version)
284 // Match returns true if the package matches a given search text
285 func (pp PluginPackage) Match(text string) bool {
286 text = strings.ToLower(text)
287 for _, t := range pp.Tags {
288 if strings.ToLower(t) == text {
292 if strings.Contains(strings.ToLower(pp.Name), text) {
296 if strings.Contains(strings.ToLower(pp.Description), text) {
303 // IsInstallable returns true if the package can be installed.
304 func (pp PluginPackage) IsInstallable(out io.Writer) error {
305 _, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
308 Range: semver.Range(func(v semver.Version) bool { return true }),
313 // SearchPlugin retrieves a list of all PluginPackages which match the given search text and
314 // could be or are already installed
315 func SearchPlugin(out io.Writer, texts []string) (plugins PluginPackages) {
316 plugins = make(PluginPackages, 0)
319 for _, pp := range GetAllPluginPackages(out) {
320 for _, text := range texts {
326 if err := pp.IsInstallable(out); err == nil {
327 plugins = append(plugins, pp)
333 func isUnknownCoreVersion() bool {
334 _, err := semver.ParseTolerant(util.Version)
338 func newStaticPluginVersion(name, version string) *PluginVersion {
339 vers, err := semver.ParseTolerant(version)
342 if vers, err = semver.ParseTolerant("0.0.0-" + version); err != nil {
343 vers = semver.MustParse("0.0.0-unknown")
346 pl := &PluginPackage{
349 pv := &PluginVersion{
353 pl.Versions = PluginVersions{pv}
357 // GetInstalledVersions returns a list of all currently installed plugins including an entry for
358 // micro itself. This can be used to resolve dependencies.
359 func GetInstalledVersions(withCore bool) PluginVersions {
360 result := PluginVersions{}
362 result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
365 for _, p := range Plugins {
369 version := GetInstalledPluginVersion(p.Name)
370 if pv := newStaticPluginVersion(p.Name, version); pv != nil {
371 result = append(result, pv)
378 // GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
379 func GetInstalledPluginVersion(name string) string {
380 plugin := ulua.L.GetGlobal(name)
381 if plugin != lua.LNil {
382 version := ulua.L.GetField(plugin, "VERSION")
383 if str, ok := version.(lua.LString); ok {
391 // DownloadAndInstall downloads and installs the given plugin and version
392 func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
393 fmt.Fprintf(out, "Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
394 resp, err := http.Get(pv.Url)
398 defer resp.Body.Close()
399 data, err := ioutil.ReadAll(resp.Body)
403 zipbuf := bytes.NewReader(data)
404 z, err := zip.NewReader(zipbuf, zipbuf.Size())
408 targetDir := filepath.Join(ConfigDir, "plug", pv.pack.Name)
409 dirPerm := os.FileMode(0755)
410 if err = os.MkdirAll(targetDir, dirPerm); err != nil {
414 // Check if all files in zip are in the same directory.
415 // this might be the case if the plugin zip contains the whole plugin dir
416 // instead of its content.
419 for i, f := range z.File {
420 parts := strings.Split(f.Name, "/")
423 } else if parts[0] != prefix {
427 // switch to true since we have at least a second file
432 // Install files and directory's
433 for _, f := range z.File {
434 parts := strings.Split(f.Name, "/")
439 targetName := filepath.Join(targetDir, filepath.Join(parts...))
440 if f.FileInfo().IsDir() {
441 if err := os.MkdirAll(targetName, dirPerm); err != nil {
445 basepath := filepath.Dir(targetName)
447 if err := os.MkdirAll(basepath, dirPerm); err != nil {
451 content, err := f.Open()
455 defer content.Close()
456 target, err := os.Create(targetName)
461 if _, err = io.Copy(target, content); err != nil {
469 func (pl PluginPackages) Get(name string) *PluginPackage {
470 for _, p := range pl {
478 func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
479 result := make(PluginVersions, 0)
482 result = append(result, p.Versions...)
487 func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
488 m := make(map[string]*PluginDependency)
489 for _, r := range req {
492 for _, o := range other {
495 m[o.Name] = &PluginDependency{
497 o.Range.AND(cur.Range),
503 result := make(PluginDependencies, 0, len(m))
504 for _, v := range m {
505 result = append(result, v)
510 // Resolve resolves dependencies between different plugins
511 func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
513 return selectedVersions, nil
515 currentRequirement, stillOpen := open[0], open[1:]
516 if currentRequirement != nil {
517 if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
518 if currentRequirement.Range(selVersion.Version) {
519 return all.Resolve(selectedVersions, stillOpen)
521 return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
523 availableVersions := all.GetAllVersions(currentRequirement.Name)
524 sort.Sort(availableVersions)
526 for _, version := range availableVersions {
527 if currentRequirement.Range(version.Version) {
528 resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
535 return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
537 return selectedVersions, nil
540 func (pv PluginVersions) install(out io.Writer) {
541 anyInstalled := false
542 currentlyInstalled := GetInstalledVersions(true)
544 for _, sel := range pv {
545 if sel.pack.Name != CorePluginName {
546 shouldInstall := true
547 if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
548 if pv.Version.NE(sel.Version) {
549 fmt.Fprintln(out, "Uninstalling", sel.pack.Name)
550 UninstallPlugin(out, sel.pack.Name)
552 shouldInstall = false
557 if err := sel.DownloadAndInstall(out); err != nil {
558 fmt.Fprintln(out, err)
566 fmt.Fprintln(out, "One or more plugins installed.")
568 fmt.Fprintln(out, "Nothing to install / update")
572 // UninstallPlugin deletes the plugin folder of the given plugin
573 func UninstallPlugin(out io.Writer, name string) {
574 for _, p := range Plugins {
580 if err := os.RemoveAll(filepath.Join(ConfigDir, "plug", p.DirName)); err != nil {
581 fmt.Fprintln(out, err)
589 // Install installs the plugin
590 func (pl PluginPackage) Install(out io.Writer) {
591 selected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
594 Range: semver.Range(func(v semver.Version) bool { return true }),
597 fmt.Fprintln(out, err)
600 selected.install(out)
603 // UpdatePlugins updates the given plugins
604 func UpdatePlugins(out io.Writer, plugins []string) {
605 // if no plugins are specified, update all installed plugins.
606 if len(plugins) == 0 {
607 for _, p := range Plugins {
608 if !p.IsEnabled() || p.Default {
611 plugins = append(plugins, p.Name)
615 fmt.Fprintln(out, "Checking for plugin updates")
616 microVersion := PluginVersions{
617 newStaticPluginVersion(CorePluginName, util.Version),
620 var updates = make(PluginDependencies, 0)
621 for _, name := range plugins {
622 pv := GetInstalledPluginVersion(name)
623 r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
625 updates = append(updates, &PluginDependency{
632 selected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)
634 fmt.Fprintln(out, err)
637 selected.install(out)
640 func PluginCommand(out io.Writer, cmd string, args []string) {
643 installedVersions := GetInstalledVersions(false)
644 for _, plugin := range args {
645 pp := GetAllPluginPackages(out).Get(plugin)
647 fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
648 } else if err := pp.IsInstallable(out); err != nil {
649 fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
651 for _, installed := range installedVersions {
652 if pp.Name == installed.Pack().Name {
653 if pp.Versions[0].Version.Compare(installed.Version) == 1 {
654 fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
656 fmt.Fprintln(out, pp.Name, " is already installed")
666 for _, plugin := range args {
667 // check if the plugin exists.
668 for _, p := range Plugins {
669 if p.Name == plugin && p.Default {
670 fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
673 if p.Name == plugin {
674 UninstallPlugin(out, plugin)
675 removed += plugin + " "
681 fmt.Fprintln(out, "Removed ", removed)
683 fmt.Fprintln(out, "No plugins removed")
686 UpdatePlugins(out, args)
688 plugins := GetInstalledVersions(false)
689 fmt.Fprintln(out, "The following plugins are currently installed:")
690 for _, p := range plugins {
691 fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
694 plugins := SearchPlugin(out, args)
695 fmt.Fprintln(out, len(plugins), " plugins found")
696 for _, p := range plugins {
697 fmt.Fprintln(out, "----------------")
698 fmt.Fprintln(out, p.String())
700 fmt.Fprintln(out, "----------------")
702 packages := GetAllPluginPackages(out)
703 fmt.Fprintln(out, "Available Plugins:")
704 for _, pkg := range packages {
705 fmt.Fprintln(out, pkg.Name)
708 fmt.Fprintln(out, "Invalid plugin command")