commit 695f7dd85c756d7e0a0244811079b6706ab9336f
parent 9185229a793f622aa1174523956e625c2ff22106
Author: Oscar Benedito <oscar@oscarbenedito.com>
Date:   Mon,  6 Nov 2023 18:41:07 +0100

Update paq-nvim

Diffstat:
M.config/nvim/lua/paq.lua | 739+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
1 file changed, 521 insertions(+), 218 deletions(-)

diff --git a/.config/nvim/lua/paq.lua b/.config/nvim/lua/paq.lua @@ -1,6 +1,6 @@ -- This file is a modified version of paq-nvim (https://github.com/savq/paq-nvim), -- which has the following notice. --- Last update: 2023-02-11 (commit 31556e9451300eb0fdc3ba45994ea83eea9f4bb2) +-- Last update: 2023-11-06 (commit 07eb567dbb70044cabc622900b2bf755f7aecb99) -- MIT License -- @@ -24,77 +24,124 @@ -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -- SOFTWARE. +-- VARS: {{{ + +---@alias Path string local uv = vim.loop -local cfg = { + +---@class setup_opts +---@field path Path +---@field opt boolean +---@field verbose boolean +---@field log Path +---@field lock Path +---@field url_format string +---@field clone_args string[] +local Config = { path = vim.fn.stdpath("data") .. "/site/pack/paqs/", opt = false, verbose = false, url_format = "https://github.com/%s.git", + log = vim.fn.stdpath(vim.fn.has("nvim-0.8") == 1 and "log" or "cache") .. "/paq.log", + lock = vim.fn.stdpath("data") .. "/paq-lock.json", + clone_args = { "--recurse-submodules", "--shallow-submodules", "--no-single-branch" } } -local logpath = vim.fn.has("nvim-0.8") == 1 and vim.fn.stdpath("log") or vim.fn.stdpath("cache") -local logfile = logpath .. "/paq.log" -local packages = {} -- "name" = {options...} pairs - --- This is done only once. Doing it for every process seems overkill -local env = {} -local envfn = vim.fn.has("nvim-0.6") == 1 and uv.os_environ or vim.fn.environ -for var, val in pairs(envfn()) do - table.insert(env, string.format("%s=%s", var, val)) -end -table.insert(env, "GIT_TERMINAL_PROMPT=0") - -vim.cmd([[ - command! -bar PaqInstall lua require('paq'):install() - command! -bar PaqUpdate lua require('paq'):update() - command! -bar PaqClean lua require('paq'):clean() - command! -bar PaqSync lua require('paq'):sync() - command! -bar PaqList lua require('paq').list() - command! -bar PaqLogOpen lua require('paq').log_open() - command! -bar PaqLogClean lua require('paq').log_clean() - command! -bar -nargs=1 -complete=customlist,v:lua.require'paq'._get_hooks PaqRunHook lua require('paq')._run_hook(<f-args>) -]]) - -local function report(op, name, res, n, total) - local messages = { - install = { ok = "Installed", err = "Failed to install" }, - update = { ok = "Updated", err = "Failed to update", nop = "(up-to-date)" }, - remove = { ok = "Removed", err = "Failed to remove" }, - hook = { ok = "Ran hook for", err = "Failed to run hook for" }, - } - local count = n and string.format(" [%d/%d]", n, total) or "" - vim.notify( - string.format(" Paq:%s %s %s", count, messages[op][res], name), - res == "err" and vim.log.levels.ERROR - ) + +---@enum Messages +local Messages = { + install = { ok = "Installed", err = "Failed to install" }, + update = { ok = "Updated", err = "Failed to update", nop = "(up-to-date)" }, + remove = { ok = "Removed", err = "Failed to remove" }, + build = { ok = "Built", err = "Failed to build" }, +} + +local Lock = {} -- Table of pgks loaded from the lockfile +local Packages = {} -- Table of pkgs loaded from the user configuration + +---@enum Status +local Status = { + INSTALLED = 0, + CLONED = 1, + UPDATED = 2, + REMOVED = 3, + TO_INSTALL = 4, + TO_MOVE = 5, + TO_RECLONE = 6, +} + +-- stylua: ignore +local Filter = { + installed = function(p) return p.status ~= Status.REMOVED and p.status ~= Status.TO_INSTALL end, + not_removed = function(p) return p.status ~= Status.REMOVED end, + removed = function(p) return p.status == Status.REMOVED end, + to_install = function(p) return p.status == Status.TO_INSTALL end, + to_update = function(p) return p.status ~= Status.REMOVED and p.status ~= Status.TO_INSTALL and not p.pin end, + to_move = function(p) return p.status == Status.TO_MOVE end, + to_reclone = function(p) return p.status == Status.TO_RECLONE end, +} + +-- Copy environment variables once. Doing it for every process seems overkill. +local Env = {} +for var, val in pairs(uv.os_environ()) do + table.insert(Env, string.format("%s=%s", var, val)) end +table.insert(Env, "GIT_TERMINAL_PROMPT=0") -local function new_counter() - return coroutine.wrap(function(op, total) - local c = { ok = 0, err = 0, nop = 0 } - while c.ok + c.err + c.nop < total do - local name, res, over_op = coroutine.yield(true) - c[res] = c[res] + 1 - if res ~= "nop" or cfg.verbose then - report(over_op or op, name, res, c.ok + c.nop, total) +-- }}} +-- UTILS: {{{ + +---@return Package +local function find_unlisted() + local unlisted = {} + -- TODO(breaking): Replace with `vim.fs.dir` + for _, packdir in pairs { "start", "opt" } do + local path = Config.path .. packdir + local handle = uv.fs_scandir(path) + while handle do + local name, t = uv.fs_scandir_next(handle) + if t == "directory" and name ~= "paq-nvim" then + local dir = path .. "/" .. name + local pkg = Packages[name] + if not pkg or pkg.dir ~= dir then + table.insert(unlisted, { name = name, dir = dir }) + end + elseif not name then + break end end - local summary = " Paq: %s complete. %d ok; %d errors;" .. (c.nop > 0 and " %d no-ops" or "") - vim.notify(string.format(summary, op, c.ok, c.err, c.nop)) - vim.cmd("packloadall! | silent! helptags ALL") - vim.cmd("doautocmd User PaqDone" .. op:gsub("^%l", string.upper)) - return true - end) + end + return unlisted end -local function call_proc(process, args, cwd, cb, print_stdout) - local log = uv.fs_open(logfile, "a+", 0x1A4) +---@param dir Path +---@return string +local function get_git_hash(dir) + local first_line = function(path) + local file = io.open(path) + if file then + local line = file:read() + file:close() + return line + end + end + local head_ref = first_line(dir .. "/.git/HEAD") + return head_ref and first_line(dir .. "/.git/" .. head_ref:gsub("ref: ", "")) +end + +---@param process string +---@param args string[] +---@param cwd string? +---@param cb function +---@param print_stdout boolean? +local function run(process, args, cwd, cb, print_stdout) + local log = uv.fs_open(Config.log, "a+", 0x1A4) local stderr = uv.new_pipe(false) stderr:open(log) local handle, pid handle, pid = uv.spawn( process, - { args = args, cwd = cwd, stdio = { nil, print_stdout and stderr, stderr }, env = env }, + { args = args, cwd = cwd, stdio = { nil, print_stdout and stderr, stderr }, env = Env }, vim.schedule_wrap(function(code) uv.fs_close(log) stderr:close() @@ -107,74 +154,56 @@ local function call_proc(process, args, cwd, cb, print_stdout) end end -local function run_hook(pkg, counter, sync) - local t = type(pkg.run) - if t == "function" then - vim.cmd("packadd " .. pkg.name) - local res = pcall(pkg.run) and "ok" or "err" - report("hook", pkg.name, res) - return counter and counter(pkg.name, res, sync) - elseif t == "string" then - local args = {} - for word in pkg.run:gmatch("%S+") do - table.insert(args, word) - end - call_proc(table.remove(args, 1), args, pkg.dir, function(ok) - local res = ok and "ok" or "err" - report("hook", pkg.name, res) - return counter and counter(pkg.name, res, sync) - end) - return true - end -end - -local function clone(pkg, counter, sync) - -- Oscar: remove "--depth=1" argument, add the if statement - local args = { "clone", pkg.url, "--recurse-submodules", "--shallow-submodules" } - if pkg.shallow then - vim.list_extend(args, { "--depth=1"}) - end - -- end - if pkg.branch then - vim.list_extend(args, { "-b", pkg.branch }) - end - vim.list_extend(args, { pkg.dir }) - call_proc("git", args, nil, function(ok) - if ok then - pkg.exists = true - pkg.status = "installed" - return pkg.run and run_hook(pkg, counter, sync) or counter(pkg.name, "ok", sync) - else - counter(pkg.name, "err", sync) +---Return an interator that walks `dir` in post-order. +---@param dir Path +local function walkdir(dir) + return coroutine.wrap(function() + local handle = uv.fs_scandir(dir) + while handle do + local name, t = uv.fs_scandir_next(handle) + if not name then + return + elseif t == "directory" then + for child, t in walkdir(dir .. "/" .. name) do + coroutine.yield(child, t) + end + end + coroutine.yield(dir .. "/" .. name, t) end end) end -local function get_git_hash(dir) - local first_line = function(path) - local file = io.open(path) - if file then - local line = file:read() - file:close() - return line +---@param dir Path +local function rmdir(dir) + for name, t in walkdir(dir) do + local ok = (t == "directory") and uv.fs_rmdir(name) or uv.fs_unlink(name) + if not ok then + return ok end end - local head_ref = first_line(dir .. "/.git/HEAD") - return head_ref and first_line(dir .. "/.git/" .. head_ref:gsub("ref: ", "")) + return uv.fs_rmdir(dir) end + +-- }}} +-- LOGGING: {{{ + +---@param pkg Package +---@param prev_hash string +---@param cur_hash string local function log_update_changes(pkg, prev_hash, cur_hash) - local output = {"\n\n" .. pkg.name .. " updated:\n"} + local output = { "\n\n" .. pkg.name .. " updated:\n" } local stdout = uv.new_pipe() local options = { - args = {"log", "--pretty=format:* %s", prev_hash .. ".." .. cur_hash}, - cwd = pkg.dir, stdio = {nil, stdout, nil}, + args = { "log", "--pretty=format:* %s", prev_hash .. ".." .. cur_hash }, + cwd = pkg.dir, + stdio = { nil, stdout, nil }, } local handle - handle, _ = uv.spawn('git', options, function(code) + handle, _ = uv.spawn("git", options, function(code) assert(code == 0, "Exited(" .. code .. ")") handle:close() - local log = uv.fs_open(logfile, "a+", 0x1A4) + local log = uv.fs_open(Config.log, "a+", 0x1A4) uv.fs_write(log, output, nil, nil) uv.fs_close(log) end) @@ -184,111 +213,370 @@ local function log_update_changes(pkg, prev_hash, cur_hash) end) end -local function pull(pkg, counter, sync) - local prev_hash = get_git_hash(pkg.dir) - call_proc("git", { "pull", "--recurse-submodules", "--update-shallow" }, pkg.dir, function(ok) +---@param name string +---@param msg_op Messages +---@param result boolean +---@param n integer +---@param total integer +local function report(name, msg_op, result, n, total) + local count = n and string.format(" [%d/%d]", n, total) or "" + vim.notify( + string.format(" Paq:%s %s %s", count, msg_op[result], name), + result == "err" and vim.log.levels.ERROR or vim.log.levels.INFO + ) +end + +---Object to track result of operations (installs, updates, etc.) +---@param total integer +---@param callback function +local function new_counter(total, callback) + return coroutine.wrap(function() + local c = { ok = 0, err = 0, nop = 0 } + while c.ok + c.err + c.nop < total do + local name, msg_op, result = coroutine.yield(true) + c[result] = c[result] + 1 + if result ~= "nop" or Config.verbose then + report(name, msg_op, result, c.ok + c.nop, total) + end + end + callback(c.ok, c.err, c.nop) + return true + end) +end + + +-- }}} +-- LOCKFILE: {{{ + +local function lock_write() + -- remove run key since can have a function in it, and + -- json.encode doesn't support functions + local pkgs = vim.deepcopy(Packages) + for p, _ in pairs(pkgs) do + pkgs[p].build = nil + end + local file = uv.fs_open(Config.lock, "w", 438) + if file then + local ok, result = pcall(vim.json.encode, pkgs) if not ok then - counter(pkg.name, "err", sync) + error(result) + end + assert(uv.fs_write(file, result)) + assert(uv.fs_close(file)) + end + Lock = Packages +end + +local function lock_load() + -- don't really know why 438 see ':h uv_fs_t' + local file = uv.fs_open(Config.lock, "r", 438) + if file then + local stat = assert(uv.fs_fstat(file)) + local data = assert(uv.fs_read(file, stat.size, 0)) + assert(uv.fs_close(file)) + local ok, result = pcall(vim.json.decode, data) + if ok then + Lock = not vim.tbl_isempty(result) and result or Packages + -- Repopulate 'build' key so 'vim.deep_equal' works + for name, pkg in pairs(result) do + pkg.build = Packages[name] and Packages[name].build or nil + end + end + else + lock_write() + Lock = Packages + end +end + +-- }}} +-- PKGS: {{{ + +---@class Package +---@field name string +---@field as string +---@field branch string +---@field shallow boolean +---@field dir string +---@field status Status +---@field hash string +---@field pin boolean +---@field build string | function +---@field url string + +---@param pkg Package +---@param counter function +---@param build_queue table +local function clone(pkg, counter, build_queue) + local args = vim.list_extend({ "clone", pkg.url }, Config.clone_args) + if pkg.shallow then + vim.list_extend(args, { "--depth=1" }) + end + if pkg.branch then + vim.list_extend(args, { "-b", pkg.branch }) + end + table.insert(args, pkg.dir) + run("git", args, nil, function(ok) + if ok then + pkg.status = Status.CLONED + lock_write() + if pkg.build then + table.insert(build_queue, pkg) + end + end + counter(pkg.name, Messages.install, ok and "ok" or "err") + end) +end + +---@param pkg Package +---@param counter function +---@param build_queue table +local function pull(pkg, counter, build_queue) + local prev_hash = Lock[pkg.name] and Lock[pkg.name].hash or pkg.hash + run("git", { "pull", "--recurse-submodules", "--update-shallow" }, pkg.dir, function(ok) + if not ok then + counter(pkg.name, Messages.update, "err") else - local cur_hash = get_git_hash(pkg.dir) + local cur_hash = pkg.hash if cur_hash ~= prev_hash then log_update_changes(pkg, prev_hash, cur_hash) - pkg.status = "updated" - return pkg.run and run_hook(pkg, counter, sync) or counter(pkg.name, "ok", sync) + pkg.status = Status.UPDATED + lock_write() + counter(pkg.name, Messages.update, "ok") + if pkg.build then + table.insert(build_queue, pkg) + end else - counter(pkg.name, "nop", sync) + counter(pkg.name, Messages.update, "nop") end end end) end -local function clone_or_pull(pkg, counter) - if pkg.exists and not pkg.pin then - pull(pkg, counter, "update") - elseif not pkg.exists then - clone(pkg, counter, "install") +---@param pkg Package +---@param counter function +---@param build_queue table +local function clone_or_pull(pkg, counter, build_queue) + if Filter.to_update(pkg) then + pull(pkg, counter, build_queue) + elseif Filter.to_install(pkg) then + clone(pkg, counter, build_queue) end end --- Return an interator that walks `dir` in post-order. -local function walkdir(dir) - return coroutine.wrap(function() - local handle = uv.fs_scandir(dir) - while handle do - local name, t = uv.fs_scandir_next(handle) - if not name then - return - elseif t == "directory" then - for child, t in walkdir(dir .. "/" .. name) do - coroutine.yield(child, t) - end - end - coroutine.yield(dir .. "/" .. name, t) - end - end) +---Move package to wanted location. +---@param src Package +---@param dst Package +local function move(src, dst) + local ok = uv.fs_rename(src.dir, dst.dir) + if ok then + dst.status = Status.INSTALLED + lock_write() + end end -local function rmdir(dir) - for name, t in walkdir(dir) do - local ok = (t == "directory") and uv.fs_rmdir(name) or uv.fs_unlink(name) - if not ok then - return ok +---@param pkg Package +local function run_build(pkg) + local t = type(pkg.build) + if t == "function" then + local ok = pcall(pkg.build) + report(pkg.name, Messages.build, ok and "ok" or "err") + elseif t == "string" and pkg.build:sub(1, 1) == ":" then + local ok = pcall(vim.cmd, pkg.build) + report(pkg.name, Messages.build, ok and "ok" or "err") + elseif t == "string" then + local args = {} + for word in pkg.build:gmatch("%S+") do + table.insert(args, word) end + run(table.remove(args, 1), args, pkg.dir, function(ok) + report(pkg.name, Messages.build, ok and "ok" or "err") + end) end - return uv.fs_rmdir(dir) end -local function find_unlisted() - local unlisted = {} - -- TODO(breaking): Replace with `vim.fs.dir` - for _, packdir in pairs { "start", "opt" } do - local path = cfg.path .. packdir - local handle = uv.fs_scandir(path) - while handle do - local name, t = uv.fs_scandir_next(handle) - if t == "directory" and name ~= "paq-nvim" then - local dir = path .. "/" .. name - local pkg = packages[name] - if not pkg or pkg.dir ~= dir then - table.insert(unlisted, { name = name, dir = dir }) - end - elseif not name then - break +---@param pkg Package +local function reclone(pkg, counter, build_queue) + local ok = rmdir(pkg.dir) + if not ok then + return + end + local args = vim.list_extend({ "clone", pkg.url }, Config.clone_args) + if pkg.branch then + vim.list_extend(args, { "-b", pkg.branch }) + end + table.insert(args, pkg.dir) + run("git", args, nil, function(ok) + if ok then + pkg.status = Status.INSTALLED + pkg.hash = get_git_hash(pkg.dir) + lock_write() + if pkg.build then + table.insert(build_queue, pkg) end end + end) +end + +local function resolve(pkg, counter, build_queue) + if Filter.to_move(pkg) then + move(pkg, Packages[pkg.name]) + elseif Filter.to_reclone(pkg) then + reclone(Packages[pkg.name], counter, build_queue) + end +end + +---@param pkg Package +local function register(pkg) + if type(pkg) == "string" then + pkg = { pkg } + end + local url = pkg.url + or (pkg[1]:match("^https?://") and pkg[1]) -- [1] is a URL + or string.format(Config.url_format, pkg[1]) -- [1] is a repository name + local name = pkg.as or url:gsub("%.git$", ""):match("/([%w-_.]+)$") -- Infer name from `url` + if not name then + return vim.notify(" Paq: Failed to parse " .. vim.inspect(pkg), vim.log.levels.ERROR) + end + local opt = pkg.opt or Config.opt and pkg.opt == nil + local dir = Config.path .. (opt and "opt/" or "start/") .. name + Packages[name] = { + name = name, + branch = pkg.branch, + shallow = pkg.shallow == nil or pkg.shallow, + dir = dir, + status = uv.fs_stat(dir) and Status.INSTALLED or Status.TO_INSTALL, + hash = get_git_hash(dir), + pin = pkg.pin, + build = pkg.build or pkg.run, + url = url, + } + if pkg.run then + vim.deprecate("`run` option", "`build`", "3.0", "Paq", false) end - return unlisted end -local function remove(p, counter) - local ok = rmdir(p.dir) - counter(p.name, ok and "ok" or "err") +---@param pkg Package +---@param counter function +local function remove(pkg, counter) + local ok = rmdir(pkg.dir) + counter(pkg.name, Messages.remove, ok and "ok" or "err") if ok then - packages[p.name] = { name = p.name, status = "removed" } + Packages[pkg.name] = { name = pkg.name, status = Status.REMOVED } + lock_write() end end -local function exe_op(op, fn, pkgs) +---@alias Operation +---| '"install"' +---| '"update"' +---| '"remove"' +---| '"build"' +---| '"resolve"' +---| '"sync"' + +---Boilerplate around operations (autocmds, counter initialization, etc.) +---@param op Operation +---@param fn function +---@param pkgs Package[] +---@param silent boolean? +local function exe_op(op, fn, pkgs, silent) if #pkgs == 0 then - vim.notify(" Paq: Nothing to " .. op) + if not silent then + vim.notify(" Paq: Nothing to " .. op) + end vim.cmd("doautocmd User PaqDone" .. op:gsub("^%l", string.upper)) return end - local counter = new_counter() - counter(op, #pkgs) + + local build_queue = {} + + local function after(ok, err, nop) + local summary = " Paq: %s complete. %d ok; %d errors;" .. (nop > 0 and " %d no-ops" or "") + vim.notify(string.format(summary, op, ok, err, nop)) + vim.cmd("packloadall! | silent! helptags ALL") + if #build_queue ~= 0 then + exe_op("build", run_build, build_queue) + end + vim.cmd("doautocmd User PaqDone" .. op:gsub("^%l", string.upper)) + end + + local counter = new_counter(#pkgs, after) + counter() -- Initialize counter + for _, pkg in pairs(pkgs) do - fn(pkg, counter) + fn(pkg, counter, build_queue) end end --- stylua: ignore -local function list() - local installed = vim.tbl_filter(function(pkg) return pkg.exists end, packages) - local removed = vim.tbl_filter(function(pkg) return pkg.status == "removed" end, packages) - local sort_by_name = function(a, b) return a.name < b.name end - table.sort(installed, sort_by_name) - table.sort(removed, sort_by_name) - local markers = { installed = "+", updated = "*" } +-- }}} +-- DIFFS: {{{ + +local function diff_gather() + local diffs = {} + for name, lock_pkg in pairs(Lock) do + local pack_pkg = Packages[name] + if pack_pkg and Filter.not_removed(lock_pkg) and not vim.deep_equal(lock_pkg, pack_pkg) then + for k, v in pairs { + dir = Status.TO_MOVE, + branch = Status.TO_RECLONE, + url = Status.TO_RECLONE, + } do + if lock_pkg[k] ~= pack_pkg[k] then + lock_pkg.status = v + table.insert(diffs, lock_pkg) + end + end + end + end + return diffs +end + + +-- }}} +-- PUBLIC API: {{{ + +local paq = {} + +---Installs all packages listed in your configuration. If a package is already +---installed, the function ignores it. If a package has a `build` argument, +---it'll be executed after the package is installed. +function paq.install() exe_op("install", clone, vim.tbl_filter(Filter.to_install, Packages)) end + +---Updates the installed packages listed in your configuration. If a package +---hasn't been installed with |PaqInstall|, the function ignores it. If a +---package had changes and it has a `build` argument, then the `build` argument +---will be executed. +function paq.update() exe_op("update", pull, vim.tbl_filter(Filter.to_update, Packages)) end + +---Removes packages found on |paq-dir| that aren't listed in your +---configuration. +function paq.clean() exe_op("remove", remove, find_unlisted()) end + +---Executes |paq.clean|, |paq.update|, and |paq.install|. Note that all +---paq operations are performed asynchronously, so messages might be printed +---out of order. +function paq:sync() + self:clean() + exe_op("sync", clone_or_pull, vim.tbl_filter(Filter.not_removed, Packages)) +end + +---@param opts setup_opts +function paq:setup(opts) + for k, v in pairs(opts) do + Config[k] = v + end + return self +end + +function paq.list() + local installed = vim.tbl_filter(Filter.installed, Lock) + local removed = vim.tbl_filter(Filter.removed, Lock) + local function sort_by_name(t) + table.sort(t, function(a, b) return a.name < b.name end) + end + sort_by_name(installed) + sort_by_name(removed) + local markers = { "+", "*" } for header, pkgs in pairs { ["Installed packages:"] = installed, ["Recently removed:"] = removed } do if #pkgs ~= 0 then print(header) @@ -299,48 +587,63 @@ local function list() end end -local function register(args) - if type(args) == "string" then - args = { args } - end - local url = args.url - or (args[1]:match("^https?://") and args[1]) -- [1] is a URL - or string.format(cfg.url_format, args[1]) -- [1] is a repository name - local name = args.as - or url:gsub("%.git$", ""):match("/([%w-_.]+)$") -- Infer name from `url` - if not name then - return vim.notify(" Paq: Failed to parse " .. vim.inspect(args), vim.log.levels.ERROR) - end - local opt = args.opt or cfg.opt and args.opt == nil - local dir = cfg.path .. (opt and "opt/" or "start/") .. name - packages[name] = { - name = name, - branch = args.branch, - dir = dir, - exists = vim.fn.isdirectory(dir) ~= 0, - status = "listed", -- TODO: should probably merge this with `exists` in the future... - pin = args.pin, - run = args.run, -- TODO(breaking): Rename - url = url, - -- Oscar: add this line - shallow = args.shallow == nil or args.shallow, - -- end - } +function paq.log_open() vim.cmd("sp " .. Config.log) end + +function paq.log_clean() return assert(uv.fs_unlink(Config.log)) and vim.notify(" Paq: log file deleted") end + +local meta = {} + +---The `paq` module is itself a callable object. It takes as argument a list of +---packages. Each element of the list can be a table or a string. +--- +---When the element is a table, the first value has to be a string with the +---name of the repository, like: `'<GitHub-username>/<repository-name>'`. +---The other key-value pairs in the table have to be named explicitly, see +---|paq-options|. When the element is a string, it works as if it was the first +---value of the table, and all other options will be set to their default +---values. +--- +---Note: Lua can elide parentheses when passing a single table argument to a +---function, so you can always call `paq` without parentheses. +---See |luaref-langFuncCalls|. +function meta:__call(pkgs) + Packages = {} + vim.tbl_map(register, pkgs) + lock_load() + exe_op("resolve", resolve, diff_gather(), true) + return self +end + +setmetatable(paq, meta) + +for cmd_name, fn in pairs { + PaqInstall = paq.install, + PaqUpdate = paq.update, + PaqClean = paq.clean, + PaqList = paq.list, + PaqLogOpen = paq.log_open, + PaqLogClean = paq.log_clean, +} +do + vim.api.nvim_create_user_command(cmd_name, function(_) fn() end, { bar = true }) end -- stylua: ignore -return setmetatable({ - install = function() exe_op("install", clone, vim.tbl_filter(function(pkg) return not pkg.exists and pkg.status ~= "removed" end, packages)) end, - update = function() exe_op("update", pull, vim.tbl_filter(function(pkg) return pkg.exists and not pkg.pin end, packages)) end, - clean = function() exe_op("remove", remove, find_unlisted()) end, - sync = function(self) self:clean() exe_op("sync", clone_or_pull, vim.tbl_filter(function(pkg) return pkg.status ~= "removed" end, packages)) end, - setup = function(self, args) for k, v in pairs(args) do cfg[k] = v end return self end, - _run_hook = function(name) return run_hook(packages[name]) end, - _get_hooks = function() return vim.tbl_keys(vim.tbl_map(function(pkg) return pkg.run end, packages)) end, - list = list, - log_open = function() vim.cmd("sp " .. logfile) end, - log_clean = function() return assert(uv.fs_unlink(logfile)) and vim.notify(" Paq: log file deleted") end, - register = register, - paq = register, -- TODO: deprecate. not urgent -}, {__call = function(self, tbl) packages = {} vim.tbl_map(register, tbl) return self end, -}) +do + local build_cmd_opts = { + bar = true, + nargs = 1, + complete = function() return vim.tbl_keys(vim.tbl_map(function(pkg) return pkg.build end, Packages)) end, + } + vim.api.nvim_create_user_command("PaqSync", function() paq:sync() end, { bar = true }) + vim.api.nvim_create_user_command("PaqBuild", function(a) run_build(Packages[a.args]) end, build_cmd_opts) + vim.api.nvim_create_user_command("PaqRunHook", function(a) + vim.deprecate("`PaqRunHook` command", "`PaqBuild`", "3.0", "Paq", false) + run_build(Packages[a.args]) + end, build_cmd_opts) +end + +return paq + +-- }}} +-- vim: foldmethod=marker foldlevel=1