1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
-- Copyright (c) 2024 squibid, see LICENSE file for more info
local mp = require('mp')
local msg = require('mp.msg')
local utils = require('mp.utils')
local proc = require('proc')
-- load the config file
local config = dofile(mp.command_native({"expand-path", "~~/eatit-cfg.lua"}))
if not config or type(config) ~= "table" then
msg.fatal("no config provided, bailing out")
return
end
local base_dir = mp.command_native({"expand-path", "~~cache/plugins"})
local packages = {}
-- make sure the base directory exists (*nix only)
if utils.file_info(base_dir) == nil then
proc.exec({ "mkdir", "-p", base_dir }, {}, function(err, message)
if err then
msg.fatal(string.format("failed to create plugin directory: %s", err))
return
end
end)
end
--- regiester a new package spec
---@param spec table package spec from config
---@return table package
local function register_pkg(spec)
if type(spec) ~= "table" then
spec = { spec }
end
local id = spec[1]
local package = packages[id]
if not package then
package = {
id = id,
exists = false,
setup = false
}
packages[id] = package
end
package.name = string.sub(package.id, string.find(package.id, "%/") + 1, #package.id)
package.url = spec.url or ("https://github.com/"..package.id..".git")
package.branch = spec.branch
package.files = spec.files
package.dir = package.files and utils.join_path(base_dir, package.name) or utils.join_path(mp.command_native({ 'expand-path', "~~/scripts" }), package.name)
package.pin = spec.pin
package.exists = utils.file_info(package.dir) ~= nil
-- validate that all files have been installed
if type(package.files) == "table" then
for filename, dest in pairs(package.files) do
if not utils.file_info(utils.join_path(package.dir, filename)) or
not utils.file_info(utils.join_path(mp.command_native({ "expand-path", dest }), filename)) then
package.exists = false
break
end
end
end
package.on_setup = spec.setup
return package
end
--- run package setup
---@param package table package
local function setup_package(package)
if type(package.on_setup) ~= "function" then
return
end
local ok, err = pcall(package.on_setup, package.dir)
if not ok then
msg.warn(string.format("error when running setup on '%s': %s", package.id, err))
return
end
package.setup = true
end
--- copy all files according to package spec
---@param package table package
local function copy_files(package)
--- copy src to dest
---@param src string path to src file
---@param dest string path to dest file
local function cp(src, dest)
local i = io.open(src, 'r')
if not i then return end
local o = io.open(dest, 'w')
if not o then return end
o:write(i:read('*a'))
o:close()
i:close()
end
if type(package.files) == "table" then
for name, loc in pairs(package.files) do
local path = mp.command_native({'expand-path', loc})
local dest = utils.join_path(path, name)
local src = utils.join_path(package.dir, name)
if not utils.file_info(src) then
msg.warn(string.format("file %s not found", name))
return
end
local ok, err = pcall(cp, src, dest)
if not ok then
msg.warn(string.format("failed to copy %s: %s", name, utils.to_string(err)))
end
end
end
end
local function validate_package()
end
--- download or update package
---@param package table package
---@param cb function callback
local function sync(package, cb)
if package.exists then
if package.pin then
cb()
return
end
--- generic error
---@param err any error
local function log_err(err)
msg.error(string.format("failed to update %s; reason: %s", package.id, err))
end
-- get current head commit hash
proc.git_rev_parse(package.dir, "HEAD", function(err, before)
if err then
log_err(before)
cb(err)
return
end
-- get the latest commit hash
proc.git_fetch(package.dir, "origin", package.branch or "HEAD", function(err, message)
if err then
log_err(message)
cb(err)
return
end
-- check the latest and current against eachother
proc.git_rev_parse(package.dir, "FETCH_HEAD", function(err, after)
if err then
log_err(after)
cb(err)
return
elseif before == after then
msg.info(string.format("skipped %s", package.id))
cb(err)
return
end
-- switch HEAD to new commit
proc.git_reset(package.dir, after, function(err, message)
if err then
log_err(message)
return
end
setup_package(package)
copy_files(package)
msg.info(string.format("updated %s; %s -> %s", package.id, before, after))
cb(err)
end)
end)
end)
end)
else
-- clone repo since it doesn't exist
proc.git_clone(package.dir, package.url, package.branch, function(err, message)
if err then
msg.error(string.format("failed to install %s; reason: %s", package.id, utils.to_string(message)))
else
setup_package(package)
copy_files(package)
package.exists = true
msg.info(string.format("installed %s", package.id))
end
cb(err)
end)
end
end
--- sync a list of plugins
---@param list table list of packages
---@param cb function callback
local function sync_list(list, cb)
local progress = 0
for i in pairs(list) do
sync(list[i], function()
progress = progress + 1
cb()
end)
end
end
--- check if package spec should be synced
---@param package table package
---@return boolean
local function should_sync(package)
if config.sync == "new" or config.sync == nil then
return not package.exists
else
return config.sync == "always"
end
end
-- register all packages
for i = 1, #config do
local ok, err = pcall(register_pkg, config[i])
if not ok then
msg.warn(string.format("%s: %s", err, config[i].as))
end
end
-- check for package updates
local targets = {}
for i in pairs(packages) do
if should_sync(packages[i]) then
targets[#targets + 1] = packages[i]
end
end
sync_list(targets, function() end)
-- register script message for keybinding
mp.register_script_message("eatit-sync", function(name, value)
sync_list(packages, function() end)
end)
|