mirror of
https://github.com/zyedidia/micro.git
synced 2025-06-19 07:15:34 -04:00
Add plugin manager
This commit is contained in:
parent
b0b5d7b392
commit
bcb1947a0a
@ -30,6 +30,7 @@ var (
|
|||||||
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
||||||
flagOptions = flag.Bool("options", false, "Show all option help")
|
flagOptions = flag.Bool("options", false, "Show all option help")
|
||||||
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
||||||
|
flagPlugin = flag.String("plugin", "", "Plugin command")
|
||||||
optionFlags map[string]*string
|
optionFlags map[string]*string
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,7 +41,6 @@ func InitFlags() {
|
|||||||
fmt.Println(" \tSpecify a custom location for the configuration directory")
|
fmt.Println(" \tSpecify a custom location for the configuration directory")
|
||||||
fmt.Println("[FILE]:LINE:COL")
|
fmt.Println("[FILE]:LINE:COL")
|
||||||
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
|
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
|
||||||
fmt.Println(" \tThis can also be done by opening file:LINE:COL")
|
|
||||||
fmt.Println("-options")
|
fmt.Println("-options")
|
||||||
fmt.Println(" \tShow all option help")
|
fmt.Println(" \tShow all option help")
|
||||||
fmt.Println("-debug")
|
fmt.Println("-debug")
|
||||||
@ -48,6 +48,14 @@ func InitFlags() {
|
|||||||
fmt.Println("-version")
|
fmt.Println("-version")
|
||||||
fmt.Println(" \tShow the version number and information")
|
fmt.Println(" \tShow the version number and information")
|
||||||
|
|
||||||
|
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
|
||||||
|
fmt.Println("-plugin install [PLUGIN]...")
|
||||||
|
fmt.Println(" \tInstall plugin(s)")
|
||||||
|
fmt.Println("-plugin remove [PLUGIN]...")
|
||||||
|
fmt.Println(" \tRemove plugin(s)")
|
||||||
|
fmt.Println("-plugin update [PLUGIN]...")
|
||||||
|
fmt.Println(" \tUpdate plugin(s) (if no argument is given, updates all plugins)")
|
||||||
|
|
||||||
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
|
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
|
||||||
fmt.Println("-option value")
|
fmt.Println("-option value")
|
||||||
fmt.Println(" \tSet `option` to `value` for this session")
|
fmt.Println(" \tSet `option` to `value` for this session")
|
||||||
@ -92,6 +100,87 @@ func InitFlags() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DoPluginFlags parses and executes any -plugin flags
|
||||||
|
func DoPluginFlags() {
|
||||||
|
if *flagPlugin != "" {
|
||||||
|
config.LoadAllPlugins()
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
switch *flagPlugin {
|
||||||
|
case "install":
|
||||||
|
installedVersions := config.GetInstalledVersions(false)
|
||||||
|
for _, plugin := range args {
|
||||||
|
pp := config.GetAllPluginPackages().Get(plugin)
|
||||||
|
if pp == nil {
|
||||||
|
fmt.Println("Unknown plugin \"" + plugin + "\"")
|
||||||
|
} else if err := pp.IsInstallable(); err != nil {
|
||||||
|
fmt.Println("Error installing ", plugin, ": ", err)
|
||||||
|
} else {
|
||||||
|
for _, installed := range installedVersions {
|
||||||
|
if pp.Name == installed.Pack().Name {
|
||||||
|
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
|
||||||
|
fmt.Println(pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
|
||||||
|
} else {
|
||||||
|
fmt.Println(pp.Name, " is already installed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pp.Install()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "remove":
|
||||||
|
removed := ""
|
||||||
|
for _, plugin := range args {
|
||||||
|
// check if the plugin exists.
|
||||||
|
for _, p := range config.Plugins {
|
||||||
|
if p.Name == plugin && p.Default {
|
||||||
|
fmt.Println("Default plugins cannot be removed, but can be disabled via settings.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p.Name == plugin {
|
||||||
|
config.UninstallPlugin(plugin)
|
||||||
|
removed += plugin + " "
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if removed != "" {
|
||||||
|
fmt.Println("Removed ", removed)
|
||||||
|
} else {
|
||||||
|
fmt.Println("No plugins removed")
|
||||||
|
}
|
||||||
|
case "update":
|
||||||
|
config.UpdatePlugins(args)
|
||||||
|
case "list":
|
||||||
|
plugins := config.GetInstalledVersions(false)
|
||||||
|
fmt.Println("The following plugins are currently installed:")
|
||||||
|
for _, p := range plugins {
|
||||||
|
fmt.Printf("%s (%s)\n", p.Pack().Name, p.Version)
|
||||||
|
}
|
||||||
|
case "search":
|
||||||
|
plugins := config.SearchPlugin(args)
|
||||||
|
fmt.Println(len(plugins), " plugins found")
|
||||||
|
for _, p := range plugins {
|
||||||
|
fmt.Println("----------------")
|
||||||
|
fmt.Println(p.String())
|
||||||
|
}
|
||||||
|
fmt.Println("----------------")
|
||||||
|
case "available":
|
||||||
|
packages := config.GetAllPluginPackages()
|
||||||
|
fmt.Println("Available Plugins:")
|
||||||
|
for _, pkg := range packages {
|
||||||
|
fmt.Println(pkg.Name)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Println("Invalid plugin command")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LoadInput determines which files should be loaded into buffers
|
// LoadInput determines which files should be loaded into buffers
|
||||||
// based on the input stored in flag.Args()
|
// based on the input stored in flag.Args()
|
||||||
func LoadInput() []*buffer.Buffer {
|
func LoadInput() []*buffer.Buffer {
|
||||||
@ -180,6 +269,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DoPluginFlags()
|
||||||
|
|
||||||
screen.Init()
|
screen.Init()
|
||||||
|
|
||||||
// If we have an error, we can exit cleanly and not completely
|
// If we have an error, we can exit cleanly and not completely
|
||||||
@ -254,7 +345,7 @@ func main() {
|
|||||||
select {
|
select {
|
||||||
case event = <-events:
|
case event = <-events:
|
||||||
action.Tabs.HandleEvent(event)
|
action.Tabs.HandleEvent(event)
|
||||||
case <-time.After(20 * time.Millisecond):
|
case <-time.After(10 * time.Millisecond):
|
||||||
// time out after 10ms
|
// time out after 10ms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
go.sum
4
go.sum
@ -50,10 +50,6 @@ github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s
|
|||||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||||
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
|
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
|
||||||
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
|
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
|
||||||
github.com/zyedidia/tcell v1.4.0 h1:uhAz+bdB3HHlVP2hff3WURkI+pERNwgVfy27oi1Gb2A=
|
|
||||||
github.com/zyedidia/tcell v1.4.0/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
|
||||||
github.com/zyedidia/tcell v1.4.1 h1:zLci8cg1SLINjwSePZ1yUWnYOnZXMyr4h+zaOvhu5K8=
|
|
||||||
github.com/zyedidia/tcell v1.4.1/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
|
||||||
github.com/zyedidia/tcell v1.4.2 h1:JWMDs6O1saINPIR5M3kNqlWJwkfnBZeZDZszEJi3BW8=
|
github.com/zyedidia/tcell v1.4.2 h1:JWMDs6O1saINPIR5M3kNqlWJwkfnBZeZDZszEJi3BW8=
|
||||||
github.com/zyedidia/tcell v1.4.2/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
github.com/zyedidia/tcell v1.4.2/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
|
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
|
||||||
|
@ -124,102 +124,7 @@ var PluginCmds = []string{"list", "info", "version"}
|
|||||||
|
|
||||||
// PluginCmd installs, removes, updates, lists, or searches for given plugins
|
// PluginCmd installs, removes, updates, lists, or searches for given plugins
|
||||||
func (h *BufPane) PluginCmd(args []string) {
|
func (h *BufPane) PluginCmd(args []string) {
|
||||||
if len(args) <= 0 {
|
InfoBar.Error("Plugin command disabled")
|
||||||
InfoBar.Error("Not enough arguments, see 'help commands'")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
valid := true
|
|
||||||
switch args[0] {
|
|
||||||
case "list":
|
|
||||||
for _, pl := range config.Plugins {
|
|
||||||
var en string
|
|
||||||
if pl.IsEnabled() {
|
|
||||||
en = "enabled"
|
|
||||||
} else {
|
|
||||||
en = "disabled"
|
|
||||||
}
|
|
||||||
WriteLog(fmt.Sprintf("%s: %s", pl.Name, en))
|
|
||||||
if pl.Default {
|
|
||||||
WriteLog(" (default)\n")
|
|
||||||
} else {
|
|
||||||
WriteLog("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WriteLog("Default plugins come pre-installed with micro.")
|
|
||||||
case "version":
|
|
||||||
if len(args) <= 1 {
|
|
||||||
InfoBar.Error("No plugin provided to give info for")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
found := false
|
|
||||||
for _, pl := range config.Plugins {
|
|
||||||
if pl.Name == args[1] {
|
|
||||||
found = true
|
|
||||||
if pl.Info == nil {
|
|
||||||
InfoBar.Message("Sorry no version for", pl.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog("Version: " + pl.Info.Vstr + "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
InfoBar.Message(args[1], "is not installed")
|
|
||||||
}
|
|
||||||
case "info":
|
|
||||||
if len(args) <= 1 {
|
|
||||||
InfoBar.Error("No plugin provided to give info for")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
found := false
|
|
||||||
for _, pl := range config.Plugins {
|
|
||||||
if pl.Name == args[1] {
|
|
||||||
found = true
|
|
||||||
if pl.Info == nil {
|
|
||||||
InfoBar.Message("Sorry no info for ", pl.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
buffer.WriteString("Name: ")
|
|
||||||
buffer.WriteString(pl.Info.Name)
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
buffer.WriteString("Description: ")
|
|
||||||
buffer.WriteString(pl.Info.Desc)
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
buffer.WriteString("Website: ")
|
|
||||||
buffer.WriteString(pl.Info.Site)
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
buffer.WriteString("Installation link: ")
|
|
||||||
buffer.WriteString(pl.Info.Install)
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
buffer.WriteString("Version: ")
|
|
||||||
buffer.WriteString(pl.Info.Vstr)
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
buffer.WriteString("Requirements:")
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
for _, r := range pl.Info.Require {
|
|
||||||
buffer.WriteString(" - ")
|
|
||||||
buffer.WriteString(r)
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog(buffer.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
InfoBar.Message(args[1], "is not installed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
InfoBar.Error("Not a valid plugin command")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if valid && h.Buf.Type != buffer.BTLog {
|
|
||||||
OpenLogBuf(h)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetabCmd changes all spaces to tabs or all tabs to spaces
|
// RetabCmd changes all spaces to tabs or all tabs to spaces
|
||||||
|
630
internal/config/plugin_installer.go
Normal file
630
internal/config/plugin_installer.go
Normal file
@ -0,0 +1,630 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
"github.com/zyedidia/json5"
|
||||||
|
ulua "github.com/zyedidia/micro/internal/lua"
|
||||||
|
"github.com/zyedidia/micro/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
allPluginPackages PluginPackages
|
||||||
|
)
|
||||||
|
|
||||||
|
// CorePluginName is a plugin dependency name for the micro core.
|
||||||
|
const CorePluginName = "micro"
|
||||||
|
|
||||||
|
// PluginChannel contains an url to a json list of PluginRepository
|
||||||
|
type PluginChannel string
|
||||||
|
|
||||||
|
// PluginChannels is a slice of PluginChannel
|
||||||
|
type PluginChannels []PluginChannel
|
||||||
|
|
||||||
|
// PluginRepository contains an url to json file containing PluginPackages
|
||||||
|
type PluginRepository string
|
||||||
|
|
||||||
|
// PluginPackage contains the meta-data of a plugin and all available versions
|
||||||
|
type PluginPackage struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Author string
|
||||||
|
Tags []string
|
||||||
|
Versions PluginVersions
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginPackages is a list of PluginPackage instances.
|
||||||
|
type PluginPackages []*PluginPackage
|
||||||
|
|
||||||
|
// PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.
|
||||||
|
type PluginVersion struct {
|
||||||
|
pack *PluginPackage
|
||||||
|
Version semver.Version
|
||||||
|
Url string
|
||||||
|
Require PluginDependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pv *PluginVersion) Pack() *PluginPackage {
|
||||||
|
return pv.pack
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginVersions is a slice of PluginVersion
|
||||||
|
type PluginVersions []*PluginVersion
|
||||||
|
|
||||||
|
// PluginDependency descripes a dependency to another plugin or micro itself.
|
||||||
|
type PluginDependency struct {
|
||||||
|
Name string
|
||||||
|
Range semver.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
// PluginDependencies is a slice of PluginDependency
|
||||||
|
type PluginDependencies []*PluginDependency
|
||||||
|
|
||||||
|
func (pp *PluginPackage) String() string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.WriteString("Plugin: ")
|
||||||
|
buf.WriteString(pp.Name)
|
||||||
|
buf.WriteRune('\n')
|
||||||
|
if pp.Author != "" {
|
||||||
|
buf.WriteString("Author: ")
|
||||||
|
buf.WriteString(pp.Author)
|
||||||
|
buf.WriteRune('\n')
|
||||||
|
}
|
||||||
|
if pp.Description != "" {
|
||||||
|
buf.WriteRune('\n')
|
||||||
|
buf.WriteString(pp.Description)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
|
||||||
|
wgQuery := new(sync.WaitGroup)
|
||||||
|
wgQuery.Add(count)
|
||||||
|
|
||||||
|
results := make(chan PluginPackages)
|
||||||
|
|
||||||
|
wgDone := new(sync.WaitGroup)
|
||||||
|
wgDone.Add(1)
|
||||||
|
var packages PluginPackages
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
results <- fetcher(i)
|
||||||
|
wgQuery.Done()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
packages = make(PluginPackages, 0)
|
||||||
|
for res := range results {
|
||||||
|
packages = append(packages, res...)
|
||||||
|
}
|
||||||
|
wgDone.Done()
|
||||||
|
}()
|
||||||
|
wgQuery.Wait()
|
||||||
|
close(results)
|
||||||
|
wgDone.Wait()
|
||||||
|
return packages
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch retrieves all available PluginPackages from the given channels
|
||||||
|
func (pc PluginChannels) Fetch() PluginPackages {
|
||||||
|
return fetchAllSources(len(pc), func(i int) PluginPackages {
|
||||||
|
return pc[i].Fetch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch retrieves all available PluginPackages from the given channel
|
||||||
|
func (pc PluginChannel) Fetch() PluginPackages {
|
||||||
|
resp, err := http.Get(string(pc))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to query plugin channel:\n", err)
|
||||||
|
return PluginPackages{}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
decoder := json5.NewDecoder(resp.Body)
|
||||||
|
|
||||||
|
var repositories []PluginRepository
|
||||||
|
if err := decoder.Decode(&repositories); err != nil {
|
||||||
|
fmt.Println("Failed to decode channel data:\n", err)
|
||||||
|
return PluginPackages{}
|
||||||
|
}
|
||||||
|
return fetchAllSources(len(repositories), func(i int) PluginPackages {
|
||||||
|
return repositories[i].Fetch()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch retrieves all available PluginPackages from the given repository
|
||||||
|
func (pr PluginRepository) Fetch() PluginPackages {
|
||||||
|
resp, err := http.Get(string(pr))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to query plugin repository:\n", err)
|
||||||
|
return PluginPackages{}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
decoder := json5.NewDecoder(resp.Body)
|
||||||
|
|
||||||
|
var plugins PluginPackages
|
||||||
|
if err := decoder.Decode(&plugins); err != nil {
|
||||||
|
fmt.Println("Failed to decode repository data:\n", err)
|
||||||
|
return PluginPackages{}
|
||||||
|
}
|
||||||
|
if len(plugins) > 0 {
|
||||||
|
return PluginPackages{plugins[0]}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
// return plugins
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals raw json to a PluginVersion
|
||||||
|
func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
|
||||||
|
var values struct {
|
||||||
|
Version semver.Version
|
||||||
|
Url string
|
||||||
|
Require map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json5.Unmarshal(data, &values); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pv.Version = values.Version
|
||||||
|
pv.Url = values.Url
|
||||||
|
pv.Require = make(PluginDependencies, 0)
|
||||||
|
|
||||||
|
for k, v := range values.Require {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals raw json to a PluginPackage
|
||||||
|
func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
|
||||||
|
var values struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Author string
|
||||||
|
Tags []string
|
||||||
|
Versions PluginVersions
|
||||||
|
}
|
||||||
|
if err := json5.Unmarshal(data, &values); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pp.Name = values.Name
|
||||||
|
pp.Description = values.Description
|
||||||
|
pp.Author = values.Author
|
||||||
|
pp.Tags = values.Tags
|
||||||
|
pp.Versions = values.Versions
|
||||||
|
for _, v := range pp.Versions {
|
||||||
|
v.pack = pp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPluginPackages gets all PluginPackages which may be available.
|
||||||
|
func GetAllPluginPackages() PluginPackages {
|
||||||
|
if allPluginPackages == nil {
|
||||||
|
getOption := func(name string) []string {
|
||||||
|
data := GetGlobalOption(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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pv PluginVersions) find(ppName string) *PluginVersion {
|
||||||
|
for _, v := range pv {
|
||||||
|
if v.pack.Name == ppName {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of pluginversions in this slice
|
||||||
|
func (pv PluginVersions) Len() int {
|
||||||
|
return len(pv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap two entries of the slice
|
||||||
|
func (pv PluginVersions) Swap(i, j int) {
|
||||||
|
pv[i], pv[j] = pv[j], pv[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns true if the version at position i is greater then the version at position j (used for sorting)
|
||||||
|
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
|
||||||
|
func (pp PluginPackage) Match(text string) bool {
|
||||||
|
text = strings.ToLower(text)
|
||||||
|
for _, t := range pp.Tags {
|
||||||
|
if strings.ToLower(t) == text {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(strings.ToLower(pp.Name), text) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(strings.ToLower(pp.Description), text) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInstallable returns true if the package can be installed.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchPlugin retrieves a list of all PluginPackages which match the given search text and
|
||||||
|
// could be or are already installed
|
||||||
|
func SearchPlugin(texts []string) (plugins PluginPackages) {
|
||||||
|
plugins = make(PluginPackages, 0)
|
||||||
|
|
||||||
|
pluginLoop:
|
||||||
|
for _, pp := range GetAllPluginPackages() {
|
||||||
|
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(util.Version)
|
||||||
|
return err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStaticPluginVersion(name, version string) *PluginVersion {
|
||||||
|
vers, err := semver.ParseTolerant(version)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if vers, err = semver.ParseTolerant("0.0.0-" + version); err != nil {
|
||||||
|
vers = semver.MustParse("0.0.0-unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pl := &PluginPackage{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
pv := &PluginVersion{
|
||||||
|
pack: pl,
|
||||||
|
Version: vers,
|
||||||
|
}
|
||||||
|
pl.Versions = PluginVersions{pv}
|
||||||
|
return pv
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInstalledVersions returns a list of all currently installed plugins including an entry for
|
||||||
|
// micro itself. This can be used to resolve dependencies.
|
||||||
|
func GetInstalledVersions(withCore bool) PluginVersions {
|
||||||
|
result := PluginVersions{}
|
||||||
|
if withCore {
|
||||||
|
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range Plugins {
|
||||||
|
version := GetInstalledPluginVersion(p.Name)
|
||||||
|
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
|
||||||
|
result = append(result, pv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
|
||||||
|
func GetInstalledPluginVersion(name string) string {
|
||||||
|
plugin := ulua.L.GetGlobal(name)
|
||||||
|
if plugin != lua.LNil {
|
||||||
|
version := ulua.L.GetField(plugin, "VERSION")
|
||||||
|
if str, ok := version.(lua.LString); ok {
|
||||||
|
return string(str)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadAndInstall downloads and installs the given plugin and version
|
||||||
|
func (pv *PluginVersion) DownloadAndInstall() error {
|
||||||
|
fmt.Printf("Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
|
||||||
|
resp, err := http.Get(pv.Url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zipbuf := bytes.NewReader(data)
|
||||||
|
z, err := zip.NewReader(zipbuf, zipbuf.Size())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targetDir := filepath.Join(ConfigDir, "plug", pv.pack.Name)
|
||||||
|
dirPerm := os.FileMode(0755)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install files and directory's
|
||||||
|
for _, f := range z.File {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
basepath := filepath.Dir(targetName)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(basepath, dirPerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer content.Close()
|
||||||
|
target, err := os.Create(targetName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer target.Close()
|
||||||
|
if _, err = io.Copy(target, content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl PluginPackages) Get(name string) *PluginPackage {
|
||||||
|
for _, p := range pl {
|
||||||
|
if p.Name == name {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
|
||||||
|
result := make(PluginVersions, 0)
|
||||||
|
p := pl.Get(name)
|
||||||
|
if p != nil {
|
||||||
|
for _, v := range p.Versions {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
|
||||||
|
m := make(map[string]*PluginDependency)
|
||||||
|
for _, r := range req {
|
||||||
|
m[r.Name] = r
|
||||||
|
}
|
||||||
|
for _, o := range other {
|
||||||
|
cur, ok := m[o.Name]
|
||||||
|
if ok {
|
||||||
|
m[o.Name] = &PluginDependency{
|
||||||
|
o.Name,
|
||||||
|
o.Range.AND(cur.Range),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m[o.Name] = o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := make(PluginDependencies, 0, len(m))
|
||||||
|
for _, v := range m {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
currentRequirement, stillOpen := open[0], open[1:]
|
||||||
|
if currentRequirement != nil {
|
||||||
|
if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
|
||||||
|
if currentRequirement.Range(selVersion.Version) {
|
||||||
|
return all.Resolve(selectedVersions, stillOpen)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return resolved, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
|
||||||
|
}
|
||||||
|
return selectedVersions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pv PluginVersions) install() {
|
||||||
|
anyInstalled := false
|
||||||
|
currentlyInstalled := GetInstalledVersions(true)
|
||||||
|
|
||||||
|
for _, sel := range pv {
|
||||||
|
if sel.pack.Name != CorePluginName {
|
||||||
|
shouldInstall := true
|
||||||
|
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
|
||||||
|
if pv.Version.NE(sel.Version) {
|
||||||
|
fmt.Println("Uninstalling", sel.pack.Name)
|
||||||
|
UninstallPlugin(sel.pack.Name)
|
||||||
|
} else {
|
||||||
|
shouldInstall = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldInstall {
|
||||||
|
if err := sel.DownloadAndInstall(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
anyInstalled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if anyInstalled {
|
||||||
|
fmt.Println("One or more plugins installed.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Nothing to install / update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UninstallPlugin deletes the plugin folder of the given plugin
|
||||||
|
func UninstallPlugin(name string) {
|
||||||
|
for _, p := range Plugins {
|
||||||
|
if p.Name == name {
|
||||||
|
p.Loaded = false
|
||||||
|
if err := os.RemoveAll(filepath.Join(ConfigDir, "plug", p.DirName)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install installs the plugin
|
||||||
|
func (pl PluginPackage) Install() {
|
||||||
|
selected, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||||
|
&PluginDependency{
|
||||||
|
Name: pl.Name,
|
||||||
|
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selected.install()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePlugins updates the given plugins
|
||||||
|
func UpdatePlugins(plugins []string) {
|
||||||
|
// if no plugins are specified, update all installed plugins.
|
||||||
|
if len(plugins) == 0 {
|
||||||
|
for _, p := range Plugins {
|
||||||
|
plugins = append(plugins, p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Checking for plugin updates")
|
||||||
|
microVersion := PluginVersions{
|
||||||
|
newStaticPluginVersion(CorePluginName, util.Version),
|
||||||
|
}
|
||||||
|
|
||||||
|
var updates = make(PluginDependencies, 0)
|
||||||
|
for _, name := range plugins {
|
||||||
|
pv := GetInstalledPluginVersion(name)
|
||||||
|
r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
|
||||||
|
if err == nil {
|
||||||
|
updates = append(updates, &PluginDependency{
|
||||||
|
Name: name,
|
||||||
|
Range: r,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := GetAllPluginPackages().Resolve(microVersion, updates)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selected.install()
|
||||||
|
}
|
56
internal/config/plugin_installer_test.go
Normal file
56
internal/config/plugin_installer_test.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
|
|
||||||
|
"github.com/zyedidia/json5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDependencyResolving(t *testing.T) {
|
||||||
|
js := `
|
||||||
|
[{
|
||||||
|
"Name": "Foo",
|
||||||
|
"Versions": [{ "Version": "1.0.0" }, { "Version": "1.5.0" },{ "Version": "2.0.0" }]
|
||||||
|
}, {
|
||||||
|
"Name": "Bar",
|
||||||
|
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": ">1.0.0 <2.0.0"} }]
|
||||||
|
}, {
|
||||||
|
"Name": "Unresolvable",
|
||||||
|
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": "<=1.0.0", "Bar": ">0.0.0"} }]
|
||||||
|
}]
|
||||||
|
`
|
||||||
|
var all PluginPackages
|
||||||
|
err := json5.Unmarshal([]byte(js), &all)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
selected, err := all.Resolve(PluginVersions{}, PluginDependencies{
|
||||||
|
&PluginDependency{"Bar", semver.MustParseRange(">=1.0.0")},
|
||||||
|
})
|
||||||
|
|
||||||
|
check := func(name, version string) {
|
||||||
|
v := selected.find(name)
|
||||||
|
expected := semver.MustParse(version)
|
||||||
|
if v == nil {
|
||||||
|
t.Errorf("Failed to resolve %s", name)
|
||||||
|
} else if expected.NE(v.Version) {
|
||||||
|
t.Errorf("%s resolved in wrong version %v", name, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
check("Foo", "1.5.0")
|
||||||
|
check("Bar", "1.0.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err = all.Resolve(PluginVersions{}, PluginDependencies{
|
||||||
|
&PluginDependency{"Unresolvable", semver.MustParseRange(">0.0.0")},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Unresolvable package resolved:", selected)
|
||||||
|
}
|
||||||
|
}
|
@ -10,9 +10,6 @@ var (
|
|||||||
ErrMissingName = errors.New("Missing or empty name field")
|
ErrMissingName = errors.New("Missing or empty name field")
|
||||||
ErrMissingDesc = errors.New("Missing or empty description field")
|
ErrMissingDesc = errors.New("Missing or empty description field")
|
||||||
ErrMissingSite = errors.New("Missing or empty website field")
|
ErrMissingSite = errors.New("Missing or empty website field")
|
||||||
ErrMissingInstall = errors.New("Missing or empty install field")
|
|
||||||
ErrMissingVstr = errors.New("Missing or empty versions field")
|
|
||||||
ErrMissingRequire = errors.New("Missing or empty require field")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PluginInfo contains all the needed info about a plugin
|
// PluginInfo contains all the needed info about a plugin
|
||||||
@ -27,19 +24,16 @@ var (
|
|||||||
// Vstr: version
|
// Vstr: version
|
||||||
// Require: list of dependencies and requirements
|
// Require: list of dependencies and requirements
|
||||||
type PluginInfo struct {
|
type PluginInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"Name"`
|
||||||
Desc string `json:"description"`
|
Desc string `json:"Description"`
|
||||||
Site string `json:"website"`
|
Site string `json:"Website"`
|
||||||
Install string `json:"install"`
|
|
||||||
Vstr string `json:"version"`
|
|
||||||
Require []string `json:"require"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPluginInfo parses a JSON input into a valid PluginInfo struct
|
// NewPluginInfo parses a JSON input into a valid PluginInfo struct
|
||||||
// Returns an error if there are any missing fields or any invalid fields
|
// Returns an error if there are any missing fields or any invalid fields
|
||||||
// There are no optional fields in a plugin info json file
|
// There are no optional fields in a plugin info json file
|
||||||
func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
||||||
var info PluginInfo
|
var info []PluginInfo
|
||||||
|
|
||||||
dec := json.NewDecoder(bytes.NewReader(data))
|
dec := json.NewDecoder(bytes.NewReader(data))
|
||||||
// dec.DisallowUnknownFields() // Force errors
|
// dec.DisallowUnknownFields() // Force errors
|
||||||
@ -48,19 +42,5 @@ func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if len(info.Name) == 0 {
|
return &info[0], nil
|
||||||
// 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.Install) == 0 {
|
|
||||||
// return nil, ErrMissingInstall
|
|
||||||
// } else if len(info.Vstr) == 0 {
|
|
||||||
// return nil, ErrMissingVstr
|
|
||||||
// } else if len(info.Require) == 0 {
|
|
||||||
// return nil, ErrMissingRequire
|
|
||||||
// }
|
|
||||||
|
|
||||||
return &info, nil
|
|
||||||
}
|
}
|
||||||
|
@ -191,12 +191,16 @@ func InitRuntimeFiles() {
|
|||||||
for _, f := range srcs {
|
for _, f := range srcs {
|
||||||
if strings.HasSuffix(f.Name(), ".lua") {
|
if strings.HasSuffix(f.Name(), ".lua") {
|
||||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
||||||
} else if f.Name() == "info.json" {
|
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), "info.json"))
|
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p.Info, _ = NewPluginInfo(data)
|
p.Info, err = NewPluginInfo(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
p.Name = p.Info.Name
|
p.Name = p.Info.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,12 +224,16 @@ func InitRuntimeFiles() {
|
|||||||
for _, f := range srcs {
|
for _, f := range srcs {
|
||||||
if strings.HasSuffix(f, ".lua") {
|
if strings.HasSuffix(f, ".lua") {
|
||||||
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
|
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
|
||||||
} else if f == "info.json" {
|
} else if strings.HasSuffix(f, ".json") {
|
||||||
data, err := Asset(filepath.Join(plugdir, d, "info.json"))
|
data, err := Asset(filepath.Join(plugdir, d, f))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p.Info, _ = NewPluginInfo(data)
|
p.Info, err = NewPluginInfo(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
p.Name = p.Info.Name
|
p.Name = p.Info.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -223,6 +223,8 @@ var defaultGlobalSettings = map[string]interface{}{
|
|||||||
"paste": false,
|
"paste": false,
|
||||||
"savehistory": true,
|
"savehistory": true,
|
||||||
"sucmd": "sudo",
|
"sucmd": "sudo",
|
||||||
|
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
|
||||||
|
"pluginrepos": []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// a list of settings that should never be globally modified
|
// a list of settings that should never be globally modified
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
VERSION = "1.0.0"
|
||||||
|
|
||||||
local uutil = import("micro/util")
|
local uutil = import("micro/util")
|
||||||
local utf8 = import("utf8")
|
local utf8 = import("utf8")
|
||||||
local autoclosePairs = {"\"\"", "''", "``", "()", "{}", "[]"}
|
local autoclosePairs = {"\"\"", "''", "``", "()", "{}", "[]"}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "autoclose",
|
|
||||||
"description": "Automatically places closing characters for quotes, parentheses, brackets, etc...",
|
|
||||||
"website": "https://github.com/zyedidia/micro",
|
|
||||||
"install": "https://github.com/zyedidia/micro",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"require": [
|
|
||||||
"micro >= 2.0.0"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,3 +1,5 @@
|
|||||||
|
VERSION = "1.0.0"
|
||||||
|
|
||||||
local util = import("micro/util")
|
local util = import("micro/util")
|
||||||
local config = import("micro/config")
|
local config = import("micro/config")
|
||||||
local buffer = import("micro/buffer")
|
local buffer = import("micro/buffer")
|
||||||
@ -102,5 +104,7 @@ function string.starts(String,Start)
|
|||||||
return string.sub(String,1,string.len(Start))==Start
|
return string.sub(String,1,string.len(Start))==Start
|
||||||
end
|
end
|
||||||
|
|
||||||
config.MakeCommand("comment", "comment.comment", config.NoComplete)
|
function init()
|
||||||
config.TryBindKey("Alt-/", "lua:comment.comment", false)
|
config.MakeCommand("comment", "comment.comment", config.NoComplete)
|
||||||
|
config.TryBindKey("Alt-/", "lua:comment.comment", false)
|
||||||
|
end
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "comment",
|
|
||||||
"description": "Support for automatically commenting blocks of code. Extensible and multiple languages supported.",
|
|
||||||
"website": "https://github.com/zyedidia/micro",
|
|
||||||
"install": "https://github.com/zyedidia/micro",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"require": [
|
|
||||||
"micro >= 2.0.0"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,3 +1,5 @@
|
|||||||
|
VERSION = "1.0.0"
|
||||||
|
|
||||||
function onBufferOpen(b)
|
function onBufferOpen(b)
|
||||||
local ft = b:FileType()
|
local ft = b:FileType()
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ftoptions",
|
|
||||||
"description": "Sets basic options based on the filetype (for example Makefiles require tabs).",
|
|
||||||
"website": "https://github.com/zyedidia/micro",
|
|
||||||
"install": "https://github.com/zyedidia/micro",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"require": [
|
|
||||||
"micro >= 2.0.0"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "linter",
|
|
||||||
"description": "Automatic code linting for a variety of languages.",
|
|
||||||
"website": "https://github.com/zyedidia/micro",
|
|
||||||
"install": "https://github.com/zyedidia/micro",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"require": [
|
|
||||||
"micro >= 2.0.0"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,3 +1,5 @@
|
|||||||
|
VERSION = "1.0.0"
|
||||||
|
|
||||||
local micro = import("micro")
|
local micro = import("micro")
|
||||||
local runtime = import("runtime")
|
local runtime = import("runtime")
|
||||||
local filepath = import("path/filepath")
|
local filepath = import("path/filepath")
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "literate",
|
|
||||||
"description": "Highlighting and language support for the Literate programming tool.",
|
|
||||||
"website": "https://github.com/zyedidia/Literate",
|
|
||||||
"install": "https://github.com/zyedidia/micro",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"require": [
|
|
||||||
"micro >= 2.0.0"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,3 +1,5 @@
|
|||||||
|
VERSION = "1.0.0"
|
||||||
|
|
||||||
local config = import("micro/config")
|
local config = import("micro/config")
|
||||||
|
|
||||||
function startswith(str, start)
|
function startswith(str, start)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
micro = import("micro")
|
VERSION = "1.0.0"
|
||||||
buffer = import("micro/buffer")
|
|
||||||
config = import("micro/config")
|
local micro = import("micro")
|
||||||
|
local buffer = import("micro/buffer")
|
||||||
|
local config = import("micro/config")
|
||||||
|
|
||||||
function init()
|
function init()
|
||||||
micro.SetStatusInfoFn("status.branch")
|
micro.SetStatusInfoFn("status.branch")
|
||||||
|
Loading…
Reference in New Issue
Block a user