Initial commit

This commit is contained in:
phosphene47
2021-11-14 04:18:54 +11:00
commit a8e711124f
4 changed files with 541 additions and 0 deletions

406
lua/dep.lua Normal file
View File

@ -0,0 +1,406 @@
local logger = require("dep/log")
local proc = require("dep/proc")
logger:open()
local base_dir
local packages, package_roots
local function register(arg)
if type(arg) ~= "table" then
arg = { arg }
end
local id = arg[1]
local package = packages[id]
if not package then
package = {
id = id,
enabled = false,
exists = false,
added = false,
configured = false,
loaded = false,
on_setup = {},
on_config = {},
on_load = {},
root = true,
dependencies = {}, -- inward edges
dependents = {}, -- outward edges
}
packages[id] = package
end
local prev_dir = package.dir -- optimization
-- meta
package.name = arg.as or package.name or id:match("^[%w-_.]+/([%w-_.]+)$")
package.url = arg.url or package.url or ("https://github.com/" .. id .. ".git")
package.branch = arg.branch or package.branch
package.dir = base_dir .. package.name
package.pin = arg.pin or package.pin
package.enabled = not arg.disabled and package.enabled
if prev_dir ~= package.dir then
package.exists = vim.fn.isdirectory(package.dir) ~= 0
package.configured = package.exists
end
table.insert(package.on_setup, arg.setup)
table.insert(package.on_config, arg.config)
table.insert(package.on_load, arg[2])
for _, req in ipairs(type(arg.requires) == "table" and arg.requires or { arg.requires }) do
local parent, child = register(req), package
parent.dependents[child.id] = child
child.dependencies[parent.id], child.root = parent, false
end
for _, dep in ipairs(type(arg.deps) == "table" and arg.deps or { arg.deps }) do
local parent, child = package, register(dep)
parent.dependents[child.id] = child
child.dependencies[parent.id], child.root = parent, false
end
end
local function register_recursive(list)
for _, arg in ipairs(list) do
register(arg)
end
for _, module in ipairs(list.modules or {}) do
if type(module) == "string" then
module = require(module)
end
register_recursive(module)
end
end
local function find_cycle()
local index = 0
local indexes = {}
local lowlink = {}
local set = {}
local stack = {}
local function connect(package)
indexes[package.id], lowlink[package.id], set[package.id] = index, index, true
index = index + 1
table.insert(stack, package)
for _, dependent in pairs(package.dependents) do
if indexes[dependent.id] == nil then
local cycle = connect(dependent)
if cycle then
return cycle
else
lowlink[package.id] = math.min(lowlink[package.id], lowlink[dependent.id])
end
elseif set[dependent.id] then
lowlink[package.id] = math.min(lowlink[package.id], indexes[dependent.id])
end
end
if lowlink[package.id] == indexes[package.id] then
local cycle = { package }
local node
repeat
node = table.remove(stack)
set[node.id] = nil
table.insert(cycle, node)
until node == package
-- only consider multi-node components
if #cycle > 2 then
return cycle
end
end
end
for _, package in pairs(packages) do
if indexes[package.id] == nil then
local cycle = connect(package)
if cycle then
return cycle
end
end
end
end
local function find_roots()
for _, package in pairs(packages) do
if package.root then
table.insert(package_roots, package)
end
end
end
local function run_hooks(package, type)
for _, cb in ipairs(package["on_" .. type]) do
local ok, err = pcall(cb)
if not ok then
return false, err
end
end
return true
end
local function ensure_added(package)
if not package.added then
local ok, err = pcall(vim.cmd, "packadd " .. package.name)
if ok then
package.added = true
else
return false, err
end
end
return true
end
local function configure_recursive(package, force)
if not package.exists or not package.enabled then
return
end
if not package.configured or force then
local ok, err = run_hooks(package, "setup")
if not ok then
logger:log("error", string.format("failed to set up %s; reason: %s", package.id, err))
return
end
ok, err = ensure_added(package)
if not ok then
logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err))
return
end
ok, err = run_hooks(package, "config")
if not ok then
logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err))
return
end
package.configured, package.loaded = true, false
force = true
end
for _, dependent in pairs(package.dependents) do
configure_recursive(dependent, force)
end
end
local function load_recursive(package, force)
if not package.exists or not package.enabled then
return
end
if not package.loaded or force then
local ok, err = ensure_added(package)
if not ok then
logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err))
return
end
ok, err = run_hooks(package, "load")
if not ok then
logger:log("error", string.format("failed to load %s; reason: %s", package.id, err))
return
end
package.loaded = true
force = true
end
for _, dependent in pairs(package.dependents) do
load_recursive(dependent, force)
end
end
local function reload_meta()
vim.cmd([[
silent! helptags ALL
silent! UpdateRemotePlugins
]])
end
local function reload_all()
for _, package in pairs(package_roots) do
configure_recursive(package)
end
for _, package in pairs(package_roots) do
load_recursive(package)
end
reload_meta()
end
local function clean()
vim.loop.fs_scandir(base_dir, function(err, handle)
if err then
logger:log("error", string.format("failed to clean; reason: %s", err))
else
local queue = {}
while handle do
local name = vim.loop.fs_scandir_next(handle)
if name then
queue[name] = base_dir .. name
else
break
end
end
for _, package in pairs(packages) do
queue[package.name] = nil
end
for name, dir in pairs(queue) do
-- todo: make this async
local ok = vim.fn.delete(dir, "rf")
if not ok then
logger:log("error", string.format("failed to delete %s", name))
end
end
end
end)
end
local function sync(package, cb)
if not package.enabled then
return
end
if package.exists then
if package.pin then
return
end
local function cb_err(err)
logger:log("error", string.format("failed to update %s; reason: %s", package.id, err))
cb(err)
end
proc.git_current_commit(package.dir, function(err, before)
if err then
cb_err(before)
else
proc.git_fetch(package.dir, package.branch or "HEAD", function(err, message)
if err then
cb_err(message)
else
proc.git_reset(package.dir, package.branch or "HEAD", function(err, message)
if err then
cb_err(message)
else
proc.get_current_commit(package.dir, function(err, after)
if err then
cb_err(after)
else
if before == after then
logger:log("skip", string.format("skipped %s", package.id))
else
package.added, package.configured = false, false
logger:log("update", string.format("updated %s", package.id))
end
end
end)
end
end)
end
end)
end
end)
else
proc.git_clone(package.dir, package.url, package.branch, function(err, message)
if err then
logger:log("error", string.format("failed to install %s; reason: %s", package.id, message))
else
package.exists, package.added, package.configured = true, false, false
logger:log("install", string.format("installed %s", package.id))
end
end)
end
end
local function sync_list(list)
local progress = 0
for _, package in ipairs(list) do
sync(package, function(err)
progress = progress + 1
if progress == #list then
clean()
reload_all()
end
end)
end
end
vim.cmd([[
command! DepSync lua require("dep").sync()
command! DepList lua require("dep").list()
command! DepClean lua require("dep").clean()
command! DepLog lua require("dep").open_log()
]])
--todo: prevent multiple execution of async routines
return setmetatable({
sync = function()
local targets = {}
for _, package in pairs(packages) do
table.insert(targets, package)
end
sync_list(targets)
end,
open_log = function()
vim.cmd("sp " .. logger.path)
end,
}, {
__call = function(config)
base_dir = config.base_dir or (vim.fn.stdpath("data") .. "/site/pack/deps/start/")
packages, package_roots = {}, {}
register_recursive({ "chiyadev/dep", modules = { config } })
local cycle = find_cycle()
if cycle then
local names = {}
for _, package in ipairs(cycle) do
table.insert(names, package.id)
end
error("circular dependency detected in package graph: " .. table.concat(names, " -> "))
end
find_roots()
reload_all()
local should_sync = function(package)
if config.sync == "new" or config.sync == nil then
return not package.exists
else
return config.sync == "always"
end
end
local targets = {}
for _, package in pairs(packages) do
if should_sync(package) then
table.insert(targets, package)
end
end
sync_list(targets)
end,
})

59
lua/dep/log.lua Normal file
View File

@ -0,0 +1,59 @@
local logger = {
path = vim.fn.stdpath("cache") .. "/dep.log",
silent = false,
}
local colors = {
install = "MoreMsg",
update = "WarningMsg",
delete = "Directory",
error = "ErrorMsg",
}
function logger:open(path)
self:close()
self.path = path or self.path
self.handle = vim.loop.fs_open(self.path, "w", 0x1A4) -- 0644
self.pipe = vim.loop.new_pipe()
self.pipe:open(self.handle)
end
function logger:close()
if self.pipe then
self.pipe:close()
self.pipe = nil
end
if self.handle then
vim.loop.fs_close(self.handle)
self.handle = nil
end
end
function logger:log(op, message, cb)
if not self.silent and colors[op] then
vim.api.nvim_echo({
{ "[dep]", "Identifier" },
{ " " },
{ message, colors[op] },
}, false, {})
end
if self.pipe then
local source = debug.getinfo(2, "Sl").short_src
local message = string.format("[%s] %s: %s\n", os.date(), source, message)
self.pipe:write(
message,
vim.schedule_wrap(function(err)
if cb then
cb(err)
end
end)
)
end
end
return logger

74
lua/dep/proc.lua Normal file
View File

@ -0,0 +1,74 @@
local logger = require("dep/log")
local proc = {}
function proc.exec(process, args, cwd, env, cb)
local out = vim.loop.new_pipe()
local buffer = {}
local handle = vim.loop.spawn(
process,
{ args = args, cwd = cwd, env = env, stdio = { nil, out, out } },
vim.schedule_wrap(function(code)
handle:close()
local output = table.concat(buffer)
logger:log(
process,
string.format('executed `%s` with args: "%s"\n%s', process, table.concat(args, '", "'), output)
)
cb(code, output)
end)
)
vim.loop.read_start(
out,
vim.schedule_wrap(function(_, data)
if data then
table.insert(buffer, data)
else
out:close()
end
end)
)
end
local git_env = { "GIT_TERMINAL_PROMPT=0" }
function proc.git_current_commit(dir, cb)
exec("git", { "rev-parse", "HEAD" }, dir, git_env, cb)
end
function proc.git_clone(dir, url, branch, cb)
local args = { "--depth=1", "--recurse-submodules", "--shallow-submodules", url, dir }
if branch then
table.insert(args, "--branch=" .. branch)
end
exec("git", args, nil, git_env, cb)
end
function proc.git_fetch(dir, branch, cb)
local args = { "--depth=1", "--recurse-submodules" }
if branch then
table.insert(args, "origin")
table.insert(args, branch)
end
exec("git", args, dir, git_env, cb)
end
function proc.git_reset(dir, branch, cb)
local args = { "--hard", "--recurse-submodules" }
if branch then
table.insert("origin/" .. branch)
end
exec("git", args, dir, git_env, cb)
end
return proc