]> git.lizzy.rs Git - micro.git/blob - internal/config/rtfiles.go
plugins: load directories that are symlinks (#2214)
[micro.git] / internal / config / rtfiles.go
1 package config
2
3 import (
4         "errors"
5         "io/ioutil"
6         "log"
7         "os"
8         "path"
9         "path/filepath"
10         "regexp"
11         "strings"
12
13         rt "github.com/zyedidia/micro/v2/runtime"
14 )
15
16 const (
17         RTColorscheme  = 0
18         RTSyntax       = 1
19         RTHelp         = 2
20         RTPlugin       = 3
21         RTSyntaxHeader = 4
22 )
23
24 var (
25         NumTypes = 5 // How many filetypes are there
26 )
27
28 type RTFiletype int
29
30 // RuntimeFile allows the program to read runtime data like colorschemes or syntax files
31 type RuntimeFile interface {
32         // Name returns a name of the file without paths or extensions
33         Name() string
34         // Data returns the content of the file.
35         Data() ([]byte, error)
36 }
37
38 // allFiles contains all available files, mapped by filetype
39 var allFiles [][]RuntimeFile
40 var realFiles [][]RuntimeFile
41
42 func init() {
43         allFiles = make([][]RuntimeFile, NumTypes)
44         realFiles = make([][]RuntimeFile, NumTypes)
45 }
46
47 // NewRTFiletype creates a new RTFiletype
48 func NewRTFiletype() int {
49         NumTypes++
50         allFiles = append(allFiles, []RuntimeFile{})
51         realFiles = append(realFiles, []RuntimeFile{})
52         return NumTypes - 1
53 }
54
55 // some file on filesystem
56 type realFile string
57
58 // some asset file
59 type assetFile string
60
61 // some file on filesystem but with a different name
62 type namedFile struct {
63         realFile
64         name string
65 }
66
67 // a file with the data stored in memory
68 type memoryFile struct {
69         name string
70         data []byte
71 }
72
73 func (mf memoryFile) Name() string {
74         return mf.name
75 }
76 func (mf memoryFile) Data() ([]byte, error) {
77         return mf.data, nil
78 }
79
80 func (rf realFile) Name() string {
81         fn := filepath.Base(string(rf))
82         return fn[:len(fn)-len(filepath.Ext(fn))]
83 }
84
85 func (rf realFile) Data() ([]byte, error) {
86         return ioutil.ReadFile(string(rf))
87 }
88
89 func (af assetFile) Name() string {
90         fn := path.Base(string(af))
91         return fn[:len(fn)-len(path.Ext(fn))]
92 }
93
94 func (af assetFile) Data() ([]byte, error) {
95         return rt.Asset(string(af))
96 }
97
98 func (nf namedFile) Name() string {
99         return nf.name
100 }
101
102 // AddRuntimeFile registers a file for the given filetype
103 func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
104         allFiles[fileType] = append(allFiles[fileType], file)
105 }
106
107 // AddRealRuntimeFile registers a file for the given filetype
108 func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
109         allFiles[fileType] = append(allFiles[fileType], file)
110         realFiles[fileType] = append(realFiles[fileType], file)
111 }
112
113 // AddRuntimeFilesFromDirectory registers each file from the given directory for
114 // the filetype which matches the file-pattern
115 func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
116         files, _ := ioutil.ReadDir(directory)
117         for _, f := range files {
118                 if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
119                         fullPath := filepath.Join(directory, f.Name())
120                         AddRealRuntimeFile(fileType, realFile(fullPath))
121                 }
122         }
123 }
124
125 // AddRuntimeFilesFromAssets registers each file from the given asset-directory for
126 // the filetype which matches the file-pattern
127 func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
128         files, err := rt.AssetDir(directory)
129         if err != nil {
130                 return
131         }
132         for _, f := range files {
133                 if ok, _ := path.Match(pattern, f); ok {
134                         AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
135                 }
136         }
137 }
138
139 // FindRuntimeFile finds a runtime file of the given filetype and name
140 // will return nil if no file was found
141 func FindRuntimeFile(fileType RTFiletype, name string) RuntimeFile {
142         for _, f := range ListRuntimeFiles(fileType) {
143                 if f.Name() == name {
144                         return f
145                 }
146         }
147         return nil
148 }
149
150 // ListRuntimeFiles lists all known runtime files for the given filetype
151 func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
152         return allFiles[fileType]
153 }
154
155 // ListRealRuntimeFiles lists all real runtime files (on disk) for a filetype
156 // these runtime files will be ones defined by the user and loaded from the config directory
157 func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
158         return realFiles[fileType]
159 }
160
161 // InitRuntimeFiles initializes all assets file and the config directory
162 func InitRuntimeFiles() {
163         add := func(fileType RTFiletype, dir, pattern string) {
164                 AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
165                 AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
166         }
167
168         add(RTColorscheme, "colorschemes", "*.micro")
169         add(RTSyntax, "syntax", "*.yaml")
170         add(RTSyntaxHeader, "syntax", "*.hdr")
171         add(RTHelp, "help", "*.md")
172
173         initlua := filepath.Join(ConfigDir, "init.lua")
174         if _, err := os.Stat(initlua); !os.IsNotExist(err) {
175                 p := new(Plugin)
176                 p.Name = "initlua"
177                 p.DirName = "initlua"
178                 p.Srcs = append(p.Srcs, realFile(initlua))
179                 Plugins = append(Plugins, p)
180         }
181
182         // Search ConfigDir for plugin-scripts
183         plugdir := filepath.Join(ConfigDir, "plug")
184         files, _ := ioutil.ReadDir(plugdir)
185
186         isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
187
188         for _, d := range files {
189                 plugpath := filepath.Join(plugdir, d.Name())
190                 if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
191                         srcs, _ := ioutil.ReadDir(plugpath)
192                         p := new(Plugin)
193                         p.Name = d.Name()
194                         p.DirName = d.Name()
195                         for _, f := range srcs {
196                                 if strings.HasSuffix(f.Name(), ".lua") {
197                                         p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
198                                 } else if strings.HasSuffix(f.Name(), ".json") {
199                                         data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
200                                         if err != nil {
201                                                 continue
202                                         }
203                                         p.Info, err = NewPluginInfo(data)
204                                         if err != nil {
205                                                 continue
206                                         }
207                                         p.Name = p.Info.Name
208                                 }
209                         }
210
211                         if !isID(p.Name) || len(p.Srcs) <= 0 {
212                                 log.Println(p.Name, "is not a plugin")
213                                 continue
214                         }
215                         Plugins = append(Plugins, p)
216                 }
217         }
218
219         plugdir = filepath.Join("runtime", "plugins")
220         if files, err := rt.AssetDir(plugdir); err == nil {
221                 for _, d := range files {
222                         if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil {
223                                 p := new(Plugin)
224                                 p.Name = d
225                                 p.DirName = d
226                                 p.Default = true
227                                 for _, f := range srcs {
228                                         if strings.HasSuffix(f, ".lua") {
229                                                 p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
230                                         } else if strings.HasSuffix(f, ".json") {
231                                                 data, err := rt.Asset(filepath.Join(plugdir, d, f))
232                                                 if err != nil {
233                                                         continue
234                                                 }
235                                                 p.Info, err = NewPluginInfo(data)
236                                                 if err != nil {
237                                                         continue
238                                                 }
239                                                 p.Name = p.Info.Name
240                                         }
241                                 }
242                                 if !isID(p.Name) || len(p.Srcs) <= 0 {
243                                         log.Println(p.Name, "is not a plugin")
244                                         continue
245                                 }
246                                 Plugins = append(Plugins, p)
247                         }
248                 }
249         }
250 }
251
252 // PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
253 func PluginReadRuntimeFile(fileType RTFiletype, name string) string {
254         if file := FindRuntimeFile(fileType, name); file != nil {
255                 if data, err := file.Data(); err == nil {
256                         return string(data)
257                 }
258         }
259         return ""
260 }
261
262 // PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
263 func PluginListRuntimeFiles(fileType RTFiletype) []string {
264         files := ListRuntimeFiles(fileType)
265         result := make([]string, len(files))
266         for i, f := range files {
267                 result[i] = f.Name()
268         }
269         return result
270 }
271
272 // PluginAddRuntimeFile adds a file to the runtime files for a plugin
273 func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) error {
274         pl := FindPlugin(plugin)
275         if pl == nil {
276                 return errors.New("Plugin " + plugin + " does not exist")
277         }
278         pldir := pl.DirName
279         fullpath := filepath.Join(ConfigDir, "plug", pldir, filePath)
280         if _, err := os.Stat(fullpath); err == nil {
281                 AddRealRuntimeFile(filetype, realFile(fullpath))
282         } else {
283                 fullpath = path.Join("runtime", "plugins", pldir, filePath)
284                 AddRuntimeFile(filetype, assetFile(fullpath))
285         }
286         return nil
287 }
288
289 // PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
290 func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) error {
291         pl := FindPlugin(plugin)
292         if pl == nil {
293                 return errors.New("Plugin " + plugin + " does not exist")
294         }
295         pldir := pl.DirName
296         fullpath := filepath.Join(ConfigDir, "plug", pldir, directory)
297         if _, err := os.Stat(fullpath); err == nil {
298                 AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
299         } else {
300                 fullpath = path.Join("runtime", "plugins", pldir, directory)
301                 AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
302         }
303         return nil
304 }
305
306 // PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
307 func PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {
308         AddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})
309 }