-- 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)