16 "github.com/blang/semver"
17 "github.com/yuin/gopher-lua"
18 "github.com/zyedidia/json5/encoding/json5"
22 allPluginPackages PluginPackages
25 // CorePluginName is a plugin dependency name for the micro core.
26 const CorePluginName = "micro"
28 // PluginChannel contains an url to a json list of PluginRepository
29 type PluginChannel string
31 // PluginChannels is a slice of PluginChannel
32 type PluginChannels []PluginChannel
34 // PluginRepository contains an url to json file containing PluginPackages
35 type PluginRepository string
37 // PluginPackage contains the meta-data of a plugin and all available versions
38 type PluginPackage struct {
43 Versions PluginVersions
46 // PluginPackages is a list of PluginPackage instances.
47 type PluginPackages []*PluginPackage
49 // PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.
50 type PluginVersion struct {
52 Version semver.Version
54 Require PluginDependencies
57 // PluginVersions is a slice of PluginVersion
58 type PluginVersions []*PluginVersion
60 // PluginDependency descripes a dependency to another plugin or micro itself.
61 type PluginDependency struct {
66 // PluginDependencies is a slice of PluginDependency
67 type PluginDependencies []*PluginDependency
69 func (pp *PluginPackage) String() string {
70 buf := new(bytes.Buffer)
71 buf.WriteString("Plugin: ")
72 buf.WriteString(pp.Name)
75 buf.WriteString("Author: ")
76 buf.WriteString(pp.Author)
79 if pp.Description != "" {
81 buf.WriteString(pp.Description)
86 func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
87 wgQuery := new(sync.WaitGroup)
90 results := make(chan PluginPackages)
92 wgDone := new(sync.WaitGroup)
94 var packages PluginPackages
95 for i := 0; i < count; i++ {
102 packages = make(PluginPackages, 0)
103 for res := range results {
104 packages = append(packages, res...)
114 // Fetch retrieves all available PluginPackages from the given channels
115 func (pc PluginChannels) Fetch() PluginPackages {
116 return fetchAllSources(len(pc), func(i int) PluginPackages {
121 // Fetch retrieves all available PluginPackages from the given channel
122 func (pc PluginChannel) Fetch() PluginPackages {
123 // messenger.AddLog(fmt.Sprintf("Fetching channel: %q", string(pc)))
124 resp, err := http.Get(string(pc))
126 TermMessage("Failed to query plugin channel:\n", err)
127 return PluginPackages{}
129 defer resp.Body.Close()
130 decoder := json5.NewDecoder(resp.Body)
132 var repositories []PluginRepository
133 if err := decoder.Decode(&repositories); err != nil {
134 TermMessage("Failed to decode channel data:\n", err)
135 return PluginPackages{}
137 return fetchAllSources(len(repositories), func(i int) PluginPackages {
138 return repositories[i].Fetch()
142 // Fetch retrieves all available PluginPackages from the given repository
143 func (pr PluginRepository) Fetch() PluginPackages {
144 // messenger.AddLog(fmt.Sprintf("Fetching repository: %q", string(pr)))
145 resp, err := http.Get(string(pr))
147 TermMessage("Failed to query plugin repository:\n", err)
148 return PluginPackages{}
150 defer resp.Body.Close()
151 decoder := json5.NewDecoder(resp.Body)
153 var plugins PluginPackages
154 if err := decoder.Decode(&plugins); err != nil {
155 TermMessage("Failed to decode repository data:\n", err)
156 return PluginPackages{}
158 if len(plugins) > 0 {
159 return PluginPackages{plugins[0]}
165 // UnmarshalJSON unmarshals raw json to a PluginVersion
166 func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
168 Version semver.Version
170 Require map[string]string
173 if err := json5.Unmarshal(data, &values); err != nil {
176 pv.Version = values.Version
178 pv.Require = make(PluginDependencies, 0)
180 for k, v := range values.Require {
181 // don't add the dependency if it's the core and
182 // we have a unknown version number.
183 // in that case just accept that dependency (which equals to not adding it.)
184 if k != CorePluginName || !isUnknownCoreVersion() {
185 if vRange, err := semver.ParseRange(v); err == nil {
186 pv.Require = append(pv.Require, &PluginDependency{k, vRange})
193 // UnmarshalJSON unmarshals raw json to a PluginPackage
194 func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
200 Versions PluginVersions
202 if err := json5.Unmarshal(data, &values); err != nil {
205 pp.Name = values.Name
206 pp.Description = values.Description
207 pp.Author = values.Author
208 pp.Tags = values.Tags
209 pp.Versions = values.Versions
210 for _, v := range pp.Versions {
216 // GetAllPluginPackages gets all PluginPackages which may be available.
217 func GetAllPluginPackages() PluginPackages {
218 if allPluginPackages == nil {
219 getOption := func(name string) []string {
220 data := GetOption(name)
221 if strs, ok := data.([]string); ok {
224 if ifs, ok := data.([]interface{}); ok {
225 result := make([]string, len(ifs))
226 for i, urlIf := range ifs {
227 if url, ok := urlIf.(string); ok {
238 channels := PluginChannels{}
239 for _, url := range getOption("pluginchannels") {
240 channels = append(channels, PluginChannel(url))
242 repos := []PluginRepository{}
243 for _, url := range getOption("pluginrepos") {
244 repos = append(repos, PluginRepository(url))
246 allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
248 return channels.Fetch()
250 return repos[i-1].Fetch()
253 return allPluginPackages
256 func (pv PluginVersions) find(ppName string) *PluginVersion {
257 for _, v := range pv {
258 if v.pack.Name == ppName {
265 // Len returns the number of pluginversions in this slice
266 func (pv PluginVersions) Len() int {
270 // Swap two entries of the slice
271 func (pv PluginVersions) Swap(i, j int) {
272 pv[i], pv[j] = pv[j], pv[i]
275 // Less returns true if the version at position i is greater then the version at position j (used for sorting)
276 func (pv PluginVersions) Less(i, j int) bool {
277 return pv[i].Version.GT(pv[j].Version)
280 // Match returns true if the package matches a given search text
281 func (pp PluginPackage) Match(text string) bool {
282 text = strings.ToLower(text)
283 for _, t := range pp.Tags {
284 if strings.ToLower(t) == text {
288 if strings.Contains(strings.ToLower(pp.Name), text) {
292 if strings.Contains(strings.ToLower(pp.Description), text) {
299 // IsInstallable returns true if the package can be installed.
300 func (pp PluginPackage) IsInstallable() error {
301 _, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
304 Range: semver.Range(func(v semver.Version) bool { return true }),
309 // SearchPlugin retrieves a list of all PluginPackages which match the given search text and
310 // could be or are already installed
311 func SearchPlugin(texts []string) (plugins PluginPackages) {
312 plugins = make(PluginPackages, 0)
315 for _, pp := range GetAllPluginPackages() {
316 for _, text := range texts {
322 if err := pp.IsInstallable(); err == nil {
323 plugins = append(plugins, pp)
329 func isUnknownCoreVersion() bool {
330 _, err := semver.ParseTolerant(Version)
334 func newStaticPluginVersion(name, version string) *PluginVersion {
335 vers, err := semver.ParseTolerant(version)
338 if vers, err = semver.ParseTolerant("0.0.0-" + version); err != nil {
339 vers = semver.MustParse("0.0.0-unknown")
342 pl := &PluginPackage{
345 pv := &PluginVersion{
349 pl.Versions = PluginVersions{pv}
353 // GetInstalledVersions returns a list of all currently installed plugins including an entry for
354 // micro itself. This can be used to resolve dependencies.
355 func GetInstalledVersions(withCore bool) PluginVersions {
356 result := PluginVersions{}
358 result = append(result, newStaticPluginVersion(CorePluginName, Version))
361 for name, lpname := range loadedPlugins {
362 version := GetInstalledPluginVersion(lpname)
363 if pv := newStaticPluginVersion(name, version); pv != nil {
364 result = append(result, pv)
371 // GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
372 func GetInstalledPluginVersion(name string) string {
373 plugin := L.GetGlobal(name)
374 if plugin != lua.LNil {
375 version := L.GetField(plugin, "VERSION")
376 if str, ok := version.(lua.LString); ok {
384 // DownloadAndInstall downloads and installs the given plugin and version
385 func (pv *PluginVersion) DownloadAndInstall() error {
386 messenger.AddLog(fmt.Sprintf("Downloading %q (%s) from %q", pv.pack.Name, pv.Version, pv.Url))
387 resp, err := http.Get(pv.Url)
391 defer resp.Body.Close()
392 data, err := ioutil.ReadAll(resp.Body)
396 zipbuf := bytes.NewReader(data)
397 z, err := zip.NewReader(zipbuf, zipbuf.Size())
401 targetDir := filepath.Join(configDir, "plugins", pv.pack.Name)
402 dirPerm := os.FileMode(0755)
403 if err = os.MkdirAll(targetDir, dirPerm); err != nil {
407 // Check if all files in zip are in the same directory.
408 // this might be the case if the plugin zip contains the whole plugin dir
409 // instead of its content.
412 for i, f := range z.File {
413 parts := strings.Split(f.Name, "/")
416 } else if parts[0] != prefix {
420 // switch to true since we have at least a second file
425 for _, f := range z.File {
426 parts := strings.Split(f.Name, "/")
431 targetName := filepath.Join(targetDir, filepath.Join(parts...))
432 if f.FileInfo().IsDir() {
433 if err := os.MkdirAll(targetName, dirPerm); err != nil {
437 content, err := f.Open()
441 defer content.Close()
442 target, err := os.Create(targetName)
447 if _, err = io.Copy(target, content); err != nil {
455 func (pl PluginPackages) Get(name string) *PluginPackage {
456 for _, p := range pl {
464 func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
465 result := make(PluginVersions, 0)
468 for _, v := range p.Versions {
469 result = append(result, v)
475 func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
476 m := make(map[string]*PluginDependency)
477 for _, r := range req {
480 for _, o := range other {
483 m[o.Name] = &PluginDependency{
485 o.Range.AND(cur.Range),
491 result := make(PluginDependencies, 0, len(m))
492 for _, v := range m {
493 result = append(result, v)
498 // Resolve resolves dependencies between different plugins
499 func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
501 return selectedVersions, nil
503 currentRequirement, stillOpen := open[0], open[1:]
504 if currentRequirement != nil {
505 if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
506 if currentRequirement.Range(selVersion.Version) {
507 return all.Resolve(selectedVersions, stillOpen)
509 return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
511 availableVersions := all.GetAllVersions(currentRequirement.Name)
512 sort.Sort(availableVersions)
514 for _, version := range availableVersions {
515 if currentRequirement.Range(version.Version) {
516 resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
523 return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
525 return selectedVersions, nil
528 func (pv PluginVersions) install() {
529 anyInstalled := false
530 currentlyInstalled := GetInstalledVersions(true)
532 for _, sel := range pv {
533 if sel.pack.Name != CorePluginName {
534 shouldInstall := true
535 if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
536 if pv.Version.NE(sel.Version) {
537 messenger.AddLog(fmt.Sprint("Uninstalling %q", sel.pack.Name))
538 UninstallPlugin(sel.pack.Name)
540 shouldInstall = false
545 if err := sel.DownloadAndInstall(); err != nil {
554 messenger.Message("One or more plugins installed. Please restart micro.")
556 messenger.AddLog("Nothing to install / update")
560 // UninstallPlugin deletes the plugin folder of the given plugin
561 func UninstallPlugin(name string) {
562 if err := os.RemoveAll(filepath.Join(configDir, "plugins", name)); err != nil {
566 delete(loadedPlugins, name)
569 // Install installs the plugin
570 func (pl PluginPackage) Install() {
571 selected, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
574 Range: semver.Range(func(v semver.Version) bool { return true }),
583 // UpdatePlugins updates the given plugins
584 func UpdatePlugins(plugins []string) {
585 // if no plugins are specified, update all installed plugins.
586 if len(plugins) == 0 {
587 for name := range loadedPlugins {
588 plugins = append(plugins, name)
592 messenger.AddLog("Checking for plugin updates")
593 microVersion := PluginVersions{
594 newStaticPluginVersion(CorePluginName, Version),
597 var updates = make(PluginDependencies, 0)
598 for _, name := range plugins {
599 pv := GetInstalledPluginVersion(name)
600 r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
602 updates = append(updates, &PluginDependency{
609 selected, err := GetAllPluginPackages().Resolve(microVersion, updates)