aboutsummaryrefslogtreecommitdiffstats
path: root/lua/dep.lua
diff options
context:
space:
mode:
authorphosphene47 <phosphene47@chiya.dev>2021-11-14 04:18:54 +1100
committerphosphene47 <phosphene47@chiya.dev>2021-11-14 04:18:54 +1100
commita8e711124f8f3999f054966064d5d3bb4056f864 (patch)
tree1e5081d8136f85a0764f05c87740256b19f29e18 /lua/dep.lua
downloaddep-a8e711124f8f3999f054966064d5d3bb4056f864.tar.gz
dep-a8e711124f8f3999f054966064d5d3bb4056f864.tar.bz2
dep-a8e711124f8f3999f054966064d5d3bb4056f864.zip
Initial commit
Diffstat (limited to 'lua/dep.lua')
-rw-r--r--lua/dep.lua406
1 files changed, 406 insertions, 0 deletions
diff --git a/lua/dep.lua b/lua/dep.lua
new file mode 100644
index 0000000..8078b62
--- /dev/null
+++ b/lua/dep.lua
@@ -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,
+})