Files
dep/lua/dep.lua
Squibid 71b78bfca4 the spec no longer fixes itself...
modules are more reliable
cleanup some typos
2025-06-23 00:18:33 -04:00

258 lines
7.1 KiB
Lua

local logger = require('dep.log')
local git = require('dep.git')
local fs = require('dep.fs')
local packager = require('dep.package')
local h = require('dep.helpers')
local spec_man = require("dep.spec")
-- all functions for convenience
local M = {}
-- TODO: actually use this (ideally make a view that shows startuptime and
-- which plugins are currently loaded)
-- performance logging
local perf = {}
-- TODO: maybe add the ability to get a lockfile? it's useful to make a config
-- rebuildable, but idk if it's actually useful for a neovim config
-- (look into how ofter people who use lazy.nvim us it)
--- get execution time of a function
---@param name string name of performance output
---@param code function function to run
---@vararg any arguments for code
function M.benchmark(name, code, ...)
local start = os.clock()
code(...)
perf[name] = os.clock() - start
end
--- recurse over all packages and register them
---@param speclist speclist table of specs
---@param overrides spec? a package spec that is used to override options
function M.registertree(speclist, overrides)
overrides = overrides or {}
-- recurse the packages
local over = overrides
for _, spec in ipairs(speclist) do
-- make sure the overrides override and take into account the packages spec
---@diagnostic disable-next-line: missing-fields
over = {
pin = overrides.pin or spec.pin,
disable = overrides.disable or spec.disable
}
-- While a package can fail to load we just don't care, it will work itself
-- out. The goal is to make sure every plugin that can load does load, not
-- keep working plugins from loading because an unrelated one doesn't load.
packager:new(spec, over)
end
if speclist.modules then
for _, module in ipairs(speclist.modules) do
local name = "<unnamed module>"
if type(module) == "string" then
if speclist.modules.prefix then
if speclist.modules.prefix:sub(#speclist.modules.prefix) ~= "." and
module:sub(1, 2) ~= "." then
module = "."..module
end
module = speclist.modules.prefix..module
end
name, module = module, require(module)
end
name = module.name or name
-- allow a module to be a spec
if spec_man.check(module, true) ~= false then
---@diagnostic disable-next-line: cast-local-type
module = { module }
end
local ok, err = pcall(M.registertree, module, overrides)
if not ok then
error(string.format("%s <- %s", err, name))
end
end
end
end
--- reload all packages in package table spec
---@param force boolean? force all packages to load
function M.reload(force)
local reloaded = packager.get_root():loadtree(force)
if reloaded then
local ok, err
M.benchmark("reload", function()
ok, err = pcall(vim.cmd,
[[
silent! helptags ALL
silent! UpdateRemotePlugins
]])
end)
if ok then
logger:log("vim", "reloaded helptags and remote plugins")
else
logger:log("error",
"failed to reload helptags and remote plugins; reason: %s", err)
end
end
end
--- sync a tree of plugins
---@param tree package[] tree of plugins
---@param cb function? callback
function M.synctree(tree, cb)
local progress = 0
local has_errors = false
local function done(err)
progress = progress + 1
has_errors = has_errors or err
if progress == #tree then
if has_errors then
logger:log("error", "there were errors during sync; see :messages or :DepLog for more information")
else
logger:log("update", "synchronized %s %s", #tree, #tree == 1 and "package" or "packages")
end
fs:clean(packager)
M.reload()
if cb then
cb()
end
end
end
for _, package in pairs(tree) do
local co = coroutine.create(function()
-- if the package provided prefers a local source then use the local
-- source instead of the git repository
if package.path then
fs:sync(package, done)
else
git.sync(package, done)
end
end)
coroutine.resume(co)
end
end
-- basically the main function of our program
return function(opts)
logger.pipe = logger:setup()
--- make comparison for table.sort
---@param a package package spec a
---@param b package package spec b
---@return boolean
local function comp(a, b)
-- NOTE: this doesn't have to be in any real order, it just has to be
-- consistant, thus we can just check if the unicode value of one package
-- id is less than the other
return a.id < b.id
end
local initialized, err = pcall(function()
packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/")
M.benchmark("load", function()
-- register all packages
local root = packager:new({
"squibid/dep",
url = "https://git.squi.bid/squibid/dep.git",
branch = "lazy",
pin = true
})
if not root then
logger:log("error", "couldn't register root package")
return
end
M.registertree(opts)
-- sort package dependencies
for _, package in pairs(packager.get_packages()) do
table.sort(package.requirements, comp)
table.sort(package.dependents, comp)
end
-- make sure there arent any circular dependencies
local ok = packager.findcycle(packager.get_packages())
if type(ok) == "table" then
logger:log("error", "found a cycle in the package spec here: %s", vim.inspect(ok))
end
end)
-- load packages
M.reload(false)
--- check if a package should be synced
---@param package table package table spec
---@return boolean sync
local function shouldsync(package)
if opts.sync == "new" or opts.sync == nil then
return not package.exists
else
return opts.sync == "always"
end
end
-- get all package that need syncing
local targets = {}
for _, package in pairs(packager.get_packages()) do
if shouldsync(package) then
table.insert(targets, package)
end
end
-- install all targets
M.synctree(targets)
end)
if not initialized then
logger:log("error", err)
end
-- add some user commands
vim.api.nvim_create_user_command("DepLog", function()
vim.cmd('vsp '..logger.path)
vim.opt_local.readonly = true
-- make the log auto update while it's open
local w = h.uv.new_fs_event()
local function watch_file(fname)
local fullpath = vim.api.nvim_call_function(
'fnamemodify', { fname, ':p' })
w:start(fullpath, {}, vim.schedule_wrap(function(...)
vim.cmd('checktime')
w:stop()
watch_file(fname)
end))
end
watch_file(logger.path)
end, {})
vim.api.nvim_create_user_command("DepSync", function()
M.synctree(packager.get_packages())
end, {})
vim.api.nvim_create_user_command("DepReload", function()
M.reload()
end, {})
vim.api.nvim_create_user_command("DepClean", function()
-- clean AND reload to make sure that all old packages are gone
fs:clean(packager)
M.reload()
end, {})
logger:cleanup()
end