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