Compare commits
17 Commits
master
...
a28fd8f2e6
Author | SHA1 | Date | |
---|---|---|---|
a28fd8f2e6 | |||
d030a5c39b | |||
381f473a15 | |||
85a0755af7 | |||
2b9498c5fd | |||
a6bf2a2637 | |||
6dd68240ac | |||
125d83ccf9 | |||
3b7963ab0a | |||
452414cafb | |||
d217ffa0b6 | |||
296dc11c93 | |||
2267d17d25 | |||
c29395004d | |||
254436c24d | |||
8bcc8bc0b1 | |||
1d0b486e08 |
4
LICENSE
4
LICENSE
@ -1,7 +1,7 @@
|
||||
MIT License
|
||||
|
||||
(c) 2021 chiya.dev
|
||||
(c) 2024 squibid
|
||||
Copyright (c) 2023 squibid
|
||||
Copyright (c) 2021-2023 chiya.dev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
248
README.md
248
README.md
@ -1,20 +1,32 @@
|
||||
# dep
|
||||
|
||||
> This readme is a work in progress.
|
||||
|
||||
A versatile, declarative and correct [neovim][1] package manager in [Lua][2].
|
||||
Originally written for personal use by [luaneko][3].
|
||||
A versatile, declarative and correct [neovim][2] package manager in [Lua][3].
|
||||
Originally written for personal use by [luaneko][4]. Adapted by [squibid][5] for
|
||||
general use.
|
||||
|
||||
What does that mean?
|
||||
|
||||
1. `versatile` - packages can be declared in any Lua file in any order of your liking.
|
||||
1. `versatile` - packages can be declared in any Lua file in any order of your
|
||||
liking.
|
||||
2. `declarative` - packages are declared using simple Lua tables.
|
||||
3. `correct` - packages are always loaded in a correct and consistent order.
|
||||
|
||||
See also squibid's [neovim-configs][5] for an example of how dep can be used in practice.
|
||||
In addition to the above dep has been built to be completely in control of you,
|
||||
the user. With the help of lazy loading you can choose when your plugin loads
|
||||
down to the finest detail (examples may be found below).
|
||||
|
||||
See also squibid's [neovim-configs][10] for an example of how dep can be used in
|
||||
practice.
|
||||
|
||||
## Requirements
|
||||
- [Neovim][1] 0.6+
|
||||
- [Git][4]
|
||||
|
||||
- [Neovim][2] 0.8+
|
||||
- [Git][6] 2.13+
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create `lua/bootstrap.lua` in your neovim config directory.
|
||||
|
||||
```lua
|
||||
@ -23,7 +35,7 @@ See also squibid's [neovim-configs][5] for an example of how dep can be used in
|
||||
local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep"
|
||||
|
||||
if vim.fn.empty(vim.fn.glob(path)) > 0 then
|
||||
vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/chiyadev/dep", path })
|
||||
vim.fn.system({ "git", "clone", "--depth=1", "https://git.squi.bid/dep", path })
|
||||
end
|
||||
|
||||
vim.cmd("packadd dep")
|
||||
@ -44,9 +56,7 @@ require "dep" {
|
||||
cleans removed packages and reloads packages as necessary.
|
||||
- `:DepClean` - cleans removed packages.
|
||||
- `:DepReload` - reloads all packages.
|
||||
- `:DepList` - prints the package list, performance metrics and dependency graphs.
|
||||
- `:DepLog` - opens the log file.
|
||||
- `:DepConfig` - opens the file that called dep, for convenience.
|
||||
|
||||
## Package specification
|
||||
|
||||
@ -58,21 +68,25 @@ A package must be declared in the following format.
|
||||
-- This is the only required field; all other fields are optional.
|
||||
"user/package",
|
||||
|
||||
-- [function] Code to run after the package is loaded into neovim.
|
||||
function()
|
||||
require "package".setup(...)
|
||||
end,
|
||||
|
||||
-- [function] Code to run before the package is loaded into neovim.
|
||||
setup = function()
|
||||
vim.g.package_config = ...
|
||||
end,
|
||||
|
||||
-- [function] Code to run after the package is loaded into neovim.
|
||||
load = function()
|
||||
require "package".setup(...)
|
||||
end,
|
||||
|
||||
-- [function] Code to run after the package is installed or updated.
|
||||
config = function()
|
||||
os.execute(...)
|
||||
end,
|
||||
|
||||
-- [function] Code used to determine when the package should be loaded.
|
||||
lazy = function(load)
|
||||
end,
|
||||
|
||||
-- [string] Overrides the short name of the package.
|
||||
-- Defaults to a substring of the full name after '/'.
|
||||
as = "custom_package",
|
||||
@ -81,6 +95,10 @@ A package must be declared in the following format.
|
||||
-- Defaults to "https://github.com/{full_name}.git".
|
||||
url = "https://git.chiya.dev/user/package.git",
|
||||
|
||||
-- [string] Overrides the source in which the package is gotten
|
||||
-- from. This is not set by default.
|
||||
path = "~/my-local-package/",
|
||||
|
||||
-- [string] Overrides the name of the branch to clone.
|
||||
-- Defaults to whatever the remote configured as their HEAD, which is usually "master".
|
||||
branch = "develop",
|
||||
@ -95,9 +113,9 @@ A package must be declared in the following format.
|
||||
-- [boolean] Prevents the package from being updated.
|
||||
pin = true,
|
||||
|
||||
-- [string|array] Specifies dependencies that must be loaded before the package.
|
||||
-- [string|array] Specifies requirements that must be loaded before the package.
|
||||
-- If given a string, it is wrapped into an array.
|
||||
requires = {...},
|
||||
reqs = {...},
|
||||
|
||||
-- [string|array] Specifies dependents that must be loaded after the package.
|
||||
-- If given a string, it is wrapped into an array.
|
||||
@ -123,7 +141,7 @@ combined into one. This is useful when declaring dependencies, which is explored
|
||||
require "dep" {
|
||||
{
|
||||
"user/package",
|
||||
requires = "user/dependency",
|
||||
reqs = "user/dependency",
|
||||
disabled = true,
|
||||
config = function()
|
||||
print "my config hook"
|
||||
@ -144,7 +162,7 @@ require "dep" {
|
||||
require "dep" {
|
||||
{
|
||||
"user/package",
|
||||
requires = { "user/dependency", "user/another_dependency" },
|
||||
reqs = { "user/dependency", "user/another_dependency" },
|
||||
deps = "user/dependent",
|
||||
disabled = true,
|
||||
config = function()
|
||||
@ -165,10 +183,10 @@ they are combined into one just like normal package specifications.
|
||||
require "dep" {
|
||||
{
|
||||
"user/package",
|
||||
requires = {
|
||||
reqs = {
|
||||
{
|
||||
"user/dependency1",
|
||||
requires = "user/dependency2"
|
||||
reqs = "user/dependency2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,7 +209,7 @@ require "dep" {
|
||||
require "dep" {
|
||||
{
|
||||
"user/dependency1",
|
||||
requires = "user/dependency2",
|
||||
reqs = "user/dependency2",
|
||||
deps = "user/package"
|
||||
}
|
||||
}
|
||||
@ -200,11 +218,11 @@ require "dep" {
|
||||
require "dep" {
|
||||
{
|
||||
"user/dependency1",
|
||||
requires = "user/dependency2"
|
||||
reqs = "user/dependency2"
|
||||
},
|
||||
{
|
||||
"user/package",
|
||||
requires = "user/dependency1"
|
||||
reqs = "user/dependency1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,11 +249,11 @@ instead of hanging or crashing.
|
||||
require "dep" {
|
||||
{
|
||||
"user/package1",
|
||||
requires = "user/package2"
|
||||
reqs = "user/package2"
|
||||
},
|
||||
{
|
||||
"user/package2",
|
||||
requires = "user/package1"
|
||||
reqs = "user/package1"
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -251,12 +269,12 @@ require "dep" {
|
||||
{
|
||||
"user/package1",
|
||||
disabled = true, -- implied
|
||||
requires = "user/dependency"
|
||||
reqs = "user/dependency"
|
||||
},
|
||||
{
|
||||
"user/package2",
|
||||
disabled = true, -- implied
|
||||
requires = "user/dependency"
|
||||
reqs = "user/dependency"
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -267,20 +285,165 @@ If a dependency fails to load for some reason, all of its dependents are guarant
|
||||
require "dep" {
|
||||
{
|
||||
"user/problematic",
|
||||
function()
|
||||
load = function()
|
||||
error("bad hook")
|
||||
end
|
||||
},
|
||||
{
|
||||
"user/dependent",
|
||||
requires = "user/problematic",
|
||||
function()
|
||||
load = function()
|
||||
print "unreachable"
|
||||
end
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Lazy loading
|
||||
|
||||
Imagine you're using [telescope.nvim][7] and you need to pull it up with a keybind,
|
||||
but you don't want to have it load before that moment. With lazy loading you may
|
||||
choose to only load it when needed using the built in lazy utils which are made
|
||||
available to you as soon as you start using the lazy option.
|
||||
|
||||
```lua
|
||||
require "dep" {
|
||||
{ "nvim-telescope/telescope.nvim",
|
||||
lazy = function(load)
|
||||
load:keymap("n", "<leader>f")
|
||||
end,
|
||||
load = function()
|
||||
require("telescope").setup {}
|
||||
vim.keymap.set("n", "<leader>f", require("telescope.builtin").find_files, {})
|
||||
end
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Say you wanted to use [gitsigns.nvim][9], but only wanted to load it when
|
||||
in a git directory OR when you call the Gitsigns command. With the power of lazy
|
||||
loading this can be accomplished by simply defining an auto command like so:
|
||||
|
||||
```lua
|
||||
require "dep" {
|
||||
{
|
||||
"lewis6991/gitsigns.nvim",
|
||||
lazy = function(load)
|
||||
-- load gitsigns if we're in a git repository
|
||||
load:auto({ "BufEnter", "BufNew" }, {
|
||||
callback = function()
|
||||
local paths = vim.fs.find({ ".git", }, { upward = true })
|
||||
if #paths > 0 then
|
||||
load:cleanup()
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- load gitsigns if the user trys to run the command
|
||||
load:cmd("Gitsigns")
|
||||
end,
|
||||
load = function()
|
||||
require("gitsigns").setup {}
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
If you're in the need of a deeper understanding of how the utils work go check
|
||||
out `lua/lazy/utils.lua` for the source code.
|
||||
|
||||
## Separating code into modules
|
||||
|
||||
Suppose you split your `init.lua` into two files `packages/search.lua` and
|
||||
`packages/vcs.lua`, which declare the packages [telescope.nvim][7] and [vim-fugitive][8] respectively.
|
||||
|
||||
```lua
|
||||
-- ~/.config/nvim/lua/packages/search.lua:
|
||||
return {
|
||||
{
|
||||
"nvim-telescope/telescope.nvim",
|
||||
reqs = "nvim-lua/plenary.nvim"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```lua
|
||||
-- ~/.config/nvim/lua/packages/vcs.lua:
|
||||
return {
|
||||
"tpope/vim-fugitive"
|
||||
}
|
||||
```
|
||||
|
||||
Package specifications from other modules can be loaded using the `modules` option.
|
||||
|
||||
```lua
|
||||
require "dep" {
|
||||
modules = {
|
||||
prefix = "packages.",
|
||||
"search",
|
||||
"vcs"
|
||||
}
|
||||
}
|
||||
|
||||
-- the above is equivalent to
|
||||
require "dep" {
|
||||
modules = {
|
||||
"packages.search",
|
||||
"packages.vcs"
|
||||
}
|
||||
}
|
||||
|
||||
-- which is equivalent to
|
||||
local packages = {}
|
||||
|
||||
for _, package in ipairs(require "packages.search") do
|
||||
table.insert(packages, package)
|
||||
end
|
||||
|
||||
for _, package in ipairs(require "packages.vcs") do
|
||||
table.insert(packages, package)
|
||||
end
|
||||
|
||||
require("dep")(packages)
|
||||
|
||||
-- which is ultimately equivalent to
|
||||
require "dep" {
|
||||
{
|
||||
"nvim-telescope/telescope.nvim",
|
||||
reqs = "nvim-lua/plenary.nvim"
|
||||
},
|
||||
"tpope/vim-fugitive"
|
||||
}
|
||||
|
||||
-- all of the above are guaranteed to load plenary.nvim before telescope.nvim.
|
||||
-- order of telescope.nvim and vim-fugitive is consistent but unspecified.
|
||||
```
|
||||
|
||||
Entire modules can be marked as disabled, which disables all top-level packages declared in that module.
|
||||
|
||||
```lua
|
||||
return {
|
||||
disable = true,
|
||||
{
|
||||
"user/package",
|
||||
disabled = true, -- implied by module
|
||||
reqs = {
|
||||
{
|
||||
"user/dependency",
|
||||
-- disabled = true -- not implied
|
||||
}
|
||||
},
|
||||
deps = {
|
||||
{
|
||||
"user/dependent",
|
||||
disabled = true -- implied by dependency
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Miscellaneous configuration
|
||||
|
||||
dep accepts configuration parameters as named fields in the package list.
|
||||
@ -293,10 +456,14 @@ require "dep" {
|
||||
-- "always": synchronize all packages on startup
|
||||
sync = "new",
|
||||
|
||||
-- [function] Callback when dep is (re)loaded
|
||||
-- if a table is returned it will be read as a table of config specs
|
||||
load = function()
|
||||
end
|
||||
-- [array] Specifies the modules to load package specifications from.
|
||||
-- Defaults to an empty table.
|
||||
-- Items can be either an array of package specifications,
|
||||
-- or a string that indicates the name of the module from which the array of package specifications is loaded.
|
||||
modules = {
|
||||
-- [string] Prefix string to prepend to all module names.
|
||||
prefix = "",
|
||||
},
|
||||
|
||||
-- list of package specs...
|
||||
}
|
||||
@ -306,8 +473,13 @@ require "dep" {
|
||||
|
||||
dep is licensed under the [MIT License](LICENSE).
|
||||
|
||||
[1]: https://neovim.io/
|
||||
[2]: https://www.lua.org/
|
||||
[3]: https://github.com/luaneko
|
||||
[4]: https://git-scm.com/
|
||||
[5]: https://git.squi.bid/nvim
|
||||
[1]: https://chiya.dev/posts/2021-11-27-why-package-manager
|
||||
[2]: https://neovim.io/
|
||||
[3]: https://www.lua.org/
|
||||
[4]: https://github.com/luaneko
|
||||
[5]: https://squi.bid
|
||||
[6]: https://git-scm.com/
|
||||
[7]: https://github.com/nvim-telescope/telescope.nvim
|
||||
[8]: https://github.com/tpope/vim-fugitive
|
||||
[9]: https://github.com/lewis6991/gitsigns.nvim
|
||||
[10]: https://git.squi.bid/nvim
|
||||
|
1010
lua/dep.lua
1010
lua/dep.lua
File diff suppressed because it is too large
Load Diff
22
lua/dep/fs.lua
Normal file
22
lua/dep/fs.lua
Normal file
@ -0,0 +1,22 @@
|
||||
local h = require('dep.helpers')
|
||||
local logger = require('dep.log')
|
||||
|
||||
local fs = {}
|
||||
|
||||
function fs:sync(package, cb)
|
||||
if not package.exists then
|
||||
fs:link(package, cb)
|
||||
end
|
||||
end
|
||||
|
||||
function fs:link(package, cb)
|
||||
h.uv.fs_symlink(package.path, package.dir, nil, function(err, _)
|
||||
if err then
|
||||
logger:log("error", "failed to symlink %s; reason: %s", package.id, err)
|
||||
else
|
||||
cb(err)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return fs
|
166
lua/dep/git.lua
Normal file
166
lua/dep/git.lua
Normal file
@ -0,0 +1,166 @@
|
||||
-- TODO: clean this up, it's a mess
|
||||
-- the nesting of all the proc calls is really annoying, and I need to find a
|
||||
-- cleaner way to do it
|
||||
|
||||
local logger = require('dep.log')
|
||||
local proc = require('dep.proc')
|
||||
|
||||
local git = {}
|
||||
|
||||
--- install or update a given package
|
||||
---@param package package package to update/install
|
||||
---@param cb function callback
|
||||
function git.sync(package, cb)
|
||||
local function sync()
|
||||
-- update or install
|
||||
if package.exists then
|
||||
git.update(package, cb)
|
||||
else
|
||||
git.install(package, cb)
|
||||
end
|
||||
end
|
||||
|
||||
-- handle arbitrary branches here
|
||||
if package.branch then
|
||||
proc.git_resolve_branch(package.url, package.branch, function(err, message)
|
||||
if not err then
|
||||
package.branch = message
|
||||
sync()
|
||||
end
|
||||
end)
|
||||
else
|
||||
sync()
|
||||
end
|
||||
end
|
||||
|
||||
--- configure a package
|
||||
---@param package table package spec
|
||||
local function configurepkg(package)
|
||||
package:runhooks("on_config")
|
||||
|
||||
logger:log("config", "package: %s configured", package.id)
|
||||
package.configured = true
|
||||
end
|
||||
|
||||
--- install a given package
|
||||
---@param package package package to install
|
||||
---@param cb function callback
|
||||
function git.install(package, cb)
|
||||
|
||||
if not package.enabled then
|
||||
cb()
|
||||
return
|
||||
end
|
||||
|
||||
proc.git_clone(package.dir, package.url, package.branch, function(err, message)
|
||||
if err then
|
||||
logger:log("error", "failed to install %s; reason: %s",
|
||||
package.id, message)
|
||||
else
|
||||
if package.commit then
|
||||
proc.git_checkout(package.dir, package.branch, package.commit, function(err, message)
|
||||
if err then
|
||||
logger:log("error", "failed to checkout %s; reason: %s", package.id, message)
|
||||
else
|
||||
package.exists = true
|
||||
package:unconfiguretree()
|
||||
logger:log("install", "installed %s", package.id)
|
||||
configurepkg(package)
|
||||
end
|
||||
end)
|
||||
else
|
||||
package.exists = true
|
||||
package:unconfiguretree()
|
||||
logger:log("install", "installed %s", package.id)
|
||||
configurepkg(package)
|
||||
end
|
||||
end
|
||||
|
||||
cb(err)
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
--- update a package
|
||||
---@param package package package to update
|
||||
---@param cb function callback
|
||||
function git.update(package, cb)
|
||||
if not package.enabled then
|
||||
cb()
|
||||
return
|
||||
end
|
||||
|
||||
local function log_err(err)
|
||||
logger:log("error", "failed to update %s; reason: %s", package.id, err)
|
||||
end
|
||||
|
||||
if package.pin then
|
||||
cb()
|
||||
return
|
||||
end
|
||||
|
||||
local function logerr(err)
|
||||
logger:log("error", "failed to update %s; reason: %s", package.id, err)
|
||||
end
|
||||
|
||||
proc.git_rev_parse(package.dir, "HEAD", function(err, before)
|
||||
if err then
|
||||
logerr(before)
|
||||
cb(err)
|
||||
else
|
||||
if package.commit then
|
||||
proc.git_checkout(package.dir, package.branch, package.commit, function(err, message)
|
||||
if err then
|
||||
log_err(message)
|
||||
cb(err)
|
||||
else
|
||||
proc.git_rev_parse(package.dir, package.commit, function(err, after)
|
||||
if err then
|
||||
log_err(after)
|
||||
cb(err)
|
||||
elseif before == after then
|
||||
logger:log("skip", "skipped %s", package.id)
|
||||
cb(err)
|
||||
else
|
||||
package:unconfiguretree()
|
||||
logger:log("update", "updated %s; %s -> %s", package.id, before, after)
|
||||
configurepkg(package)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
else
|
||||
proc.git_fetch(package.dir, "origin", package.branch or "HEAD", function(err, message)
|
||||
if err then
|
||||
log_err(message)
|
||||
cb(err)
|
||||
else
|
||||
proc.git_rev_parse(package.dir, "FETCH_HEAD^{commit}", function(err, after)
|
||||
if err then
|
||||
log_err(after)
|
||||
cb(err)
|
||||
elseif before == after then
|
||||
logger:log("skip", "skipped %s", package.id)
|
||||
cb(err)
|
||||
else
|
||||
proc.git_reset(package.dir, after, function(err, message)
|
||||
if err then
|
||||
log_err(message)
|
||||
else
|
||||
package:unconfiguretree()
|
||||
logger:log("update", "updated %s; %s -> %s", package.id, before, after)
|
||||
configurepkg(package)
|
||||
end
|
||||
|
||||
cb(err)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return git
|
4
lua/dep/helpers.lua
Normal file
4
lua/dep/helpers.lua
Normal file
@ -0,0 +1,4 @@
|
||||
return {
|
||||
-- vim.loop was depricated in nvim 0.10
|
||||
uv = vim.uv or vim.loop
|
||||
}
|
171
lua/dep/log.lua
171
lua/dep/log.lua
@ -1,23 +1,30 @@
|
||||
--
|
||||
-- Copyright (c) 2022 chiya.dev
|
||||
--
|
||||
-- Use of this source code is governed by the MIT License
|
||||
-- which can be found in the LICENSE file and at:
|
||||
--
|
||||
-- https://chiya.dev/licenses/mit.txt
|
||||
--
|
||||
local vim, setmetatable, pcall, debug, string, os, assert = vim, setmetatable, pcall, debug, string, os, assert
|
||||
local h = require('dep.helpers')
|
||||
|
||||
local logger = {}
|
||||
|
||||
logger.stage_colors = {
|
||||
skip = "Comment",
|
||||
clean = "Boolean",
|
||||
install = "MoreMsg",
|
||||
update = "WarningMsg",
|
||||
delete = "Directory",
|
||||
error = "ErrorMsg",
|
||||
}
|
||||
|
||||
--- create the default logging path
|
||||
---@return string path to the logfile
|
||||
local function default_log_path()
|
||||
-- ensure cache directory exists (#5)
|
||||
-- create cache directory and chmod it if it doesn't already exist
|
||||
local path = vim.fn.stdpath("cache")
|
||||
if not vim.loop.fs_stat(path) then
|
||||
vim.loop.fs_mkdir(path, 0x1ff) -- 0777
|
||||
if not h.uv.fs_stat(path) then
|
||||
h.uv.fs_mkdir(path, 0x1ff) -- 0777
|
||||
end
|
||||
|
||||
return path .. "/dep.log"
|
||||
return vim.fs.normalize(path).."/dep.log"
|
||||
end
|
||||
|
||||
--- attempt to format a string
|
||||
---@vararg string formating args
|
||||
local function try_format(...)
|
||||
local ok, s = pcall(string.format, ...)
|
||||
if ok then
|
||||
@ -25,85 +32,69 @@ local function try_format(...)
|
||||
end
|
||||
end
|
||||
|
||||
--- Writes logs to a file and prints pretty status messages.
|
||||
local Logger = setmetatable({
|
||||
__metatable = "Logger",
|
||||
__index = {
|
||||
--- Prints a message associated with a stage.
|
||||
log = function(self, stage, message, ...)
|
||||
-- calling function
|
||||
local source = debug.getinfo(2, "Sl").short_src
|
||||
--- setup all logging stuff
|
||||
---@param path string|nil optional alternative path for the log file
|
||||
---@return table
|
||||
function logger:setup(path)
|
||||
logger.path = path or default_log_path()
|
||||
local pipe
|
||||
|
||||
-- format or stringify message
|
||||
if type(message) == "string" then
|
||||
message = try_format(message, ...) or message
|
||||
else
|
||||
message = vim.inspect(message)
|
||||
logger.handle = assert(h.uv.fs_open(logger.path, "w", 0x1a4)) -- 0644
|
||||
pipe = h.uv.new_pipe()
|
||||
pipe:open(logger.handle)
|
||||
|
||||
return pipe
|
||||
end
|
||||
|
||||
--- log a message
|
||||
---@param level string error level
|
||||
---@param message any string message to send
|
||||
---@vararg any options to go into the message
|
||||
function logger:log(level, message, ...)
|
||||
-- make sure the message string is actually a string, and formatted
|
||||
-- appropriately
|
||||
if type(message) == "string" then
|
||||
message = try_format(message, ...) or message
|
||||
else
|
||||
message = vim.inspect(message)
|
||||
end
|
||||
|
||||
-- get debug info about the current function
|
||||
local source = debug.getinfo(2, "Sl")
|
||||
|
||||
-- schedule a log message to be sent to vim, and the log file
|
||||
vim.schedule(function()
|
||||
if not logger.silent then
|
||||
if level == "error" then
|
||||
vim.api.nvim_echo({ { string.format("[dep] %s", message) } }, true, { err = true })
|
||||
elseif logger.stage_colors[level] then
|
||||
vim.api.nvim_echo({
|
||||
{ "[dep]", "Identifier" },
|
||||
{ " " },
|
||||
{ message, logger.stage_colors[level] },
|
||||
}, true, {})
|
||||
end
|
||||
end
|
||||
|
||||
-- print and write must be done on the main event loop
|
||||
vim.schedule(function()
|
||||
if not self.silent then
|
||||
if stage == "error" then
|
||||
vim.api.nvim_err_writeln(string.format("[dep] %s", message))
|
||||
elseif self.stage_colors[stage] then
|
||||
vim.api.nvim_echo({
|
||||
{ "[dep]", "Identifier" },
|
||||
{ " " },
|
||||
{ message, self.stage_colors[stage] },
|
||||
}, true, {})
|
||||
end
|
||||
end
|
||||
-- write to the pipe if it's open
|
||||
if logger.pipe then
|
||||
logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"), source.short_src:gsub('.*%/', ''), source.currentline, message))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if self.pipe then
|
||||
self.pipe:write(string.format("[%s] %s: %s\n", os.date(), source, message))
|
||||
end
|
||||
end)
|
||||
end,
|
||||
--- cleanup all logging stuff
|
||||
---@param pipe table? pipe
|
||||
---@param handle table? handle
|
||||
function logger:cleanup(pipe, handle)
|
||||
if pipe then
|
||||
pipe:close()
|
||||
pipe = nil
|
||||
end
|
||||
if handle then
|
||||
h.uv.fs_close(logger.handle)
|
||||
handle = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Closes the log file handle.
|
||||
close = function(self)
|
||||
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,
|
||||
},
|
||||
}, {
|
||||
--- Constructs a new `Logger`.
|
||||
__call = function(mt, path)
|
||||
path = path or default_log_path()
|
||||
|
||||
-- clear and open log file
|
||||
local handle = assert(vim.loop.fs_open(path, "w", 0x1a4)) -- 0644
|
||||
local pipe = vim.loop.new_pipe()
|
||||
pipe:open(handle)
|
||||
|
||||
return setmetatable({
|
||||
path = path,
|
||||
handle = handle,
|
||||
pipe = pipe,
|
||||
silent = false,
|
||||
|
||||
-- TODO: This looks good for me ;) but it should have proper vim color mapping for other people.
|
||||
stage_colors = {
|
||||
skip = "Comment",
|
||||
clean = "Boolean",
|
||||
install = "MoreMsg",
|
||||
update = "WarningMsg",
|
||||
delete = "Directory",
|
||||
error = "ErrorMsg",
|
||||
},
|
||||
}, mt)
|
||||
end,
|
||||
})
|
||||
|
||||
return {
|
||||
Logger = Logger,
|
||||
global = Logger(),
|
||||
}
|
||||
return logger
|
||||
|
@ -1,258 +1,599 @@
|
||||
--
|
||||
-- Copyright (c) 2022 chiya.dev
|
||||
--
|
||||
-- Use of this source code is governed by the MIT License
|
||||
-- which can be found in the LICENSE file and at:
|
||||
--
|
||||
-- https://chiya.dev/licenses/mit.txt
|
||||
--
|
||||
local require, type, setmetatable, error, table, assert, math, os, debug =
|
||||
require, type, setmetatable, error, table, assert, math, os, debug
|
||||
local logger = require("dep.log").global
|
||||
local logger = require('dep.log')
|
||||
|
||||
local function parse_name_from_id(id)
|
||||
local name = id:match("^[%w-_.]+/([%w-_.]+)$")
|
||||
if name then
|
||||
return name
|
||||
else
|
||||
error(string.format('invalid package name "%s"; must be in the format "user/package"', id))
|
||||
-- TODO: allow specificying a path to use instead of cloning
|
||||
-- like so: @field path string? path to override other url
|
||||
-- I might have to deal with removing dead links and what not
|
||||
--
|
||||
-- I think it would be best to link the desired directory to the
|
||||
-- neovim package directory, but idk if that will be easy enough
|
||||
-- we'll see
|
||||
|
||||
---@class spec
|
||||
---@field [1] string id
|
||||
---@field setup function? code to run before the package is loaded
|
||||
---@field load function? code to run after the package is loaded
|
||||
---@field config function? code to run after the package is installed/updated
|
||||
---@field lazy function? code to run which determines when the package is loaded
|
||||
---@field as string? overrides the short name of the package which is usually set
|
||||
--- to a substring of all the chars after '/' in spec[1]
|
||||
---@field url string? the url to the git repository hosting the package
|
||||
---@field path string? path to local version of plugin, overrides the url
|
||||
---@field branch string? the branch which the version of the package resides
|
||||
---@field commit string? the commit which the version of the package resides
|
||||
---@field disable boolean? if true disables the package from being loaded
|
||||
---@field pin boolean? if true disables the package from being installed/updated
|
||||
---@field reqs spec|spec[]|string? packages that this package requires
|
||||
---@field deps spec|spec[]|string? packages that depend on this package
|
||||
|
||||
---@class package
|
||||
---@field id string id of the package
|
||||
---@field enabled boolean whether it's going to be used
|
||||
---@field exists boolean if the package exists on the filesystem
|
||||
---@field lazy boolean if the package is lazy loaded in any way
|
||||
---@field added boolean if the package has been added in vim
|
||||
---@field configured boolean if the package has been configured
|
||||
---@field lazied boolean if the packages lazy loading has been set
|
||||
---@field loaded boolean if a package has been loaded
|
||||
---@field subtree_loaded boolean is the subtree has been loaded
|
||||
---@field on_config function[] table of functions to run on config
|
||||
---@field on_setup function[] table of function to run on setup
|
||||
---@field on_load function[] table of functions to run on load
|
||||
---@field lazy_load function[] table of functions to run which will tell the package when to load
|
||||
---@field requirements package[] this package's requirements
|
||||
---@field dependents package[] packages that require this package
|
||||
---@field perf table performance metrics for the package
|
||||
---@field name string the name of the package
|
||||
---@field url string the url of the package
|
||||
---@field path string? the path of the package which overrides the url
|
||||
---@field branch string the branch of the package
|
||||
---@field dir string the directory of the package
|
||||
---@field commit string the commit of the package
|
||||
---@field pin boolean whether to pin the package or not
|
||||
local package = {}
|
||||
|
||||
--- the base directory for the packages
|
||||
---@type string
|
||||
local base_dir
|
||||
|
||||
--- the root package
|
||||
---@type package
|
||||
local root
|
||||
|
||||
--- list of all package in dep
|
||||
---@type package[]
|
||||
local packages = {}
|
||||
|
||||
--- check a spec to see if it's correct
|
||||
---@param spec spec|string the specification to check
|
||||
---@return spec|false spec if the spec is ok or false
|
||||
local function check_spec(spec)
|
||||
-- make sure spec is a table
|
||||
if type(spec) == "string" then
|
||||
spec = { spec }
|
||||
end
|
||||
|
||||
-- make sure all the data is correct
|
||||
do -- spec[1]
|
||||
if type(spec[1]) ~= "string" then
|
||||
logger:log("spec", "spec[1] must be a string")
|
||||
return false
|
||||
end
|
||||
|
||||
local name = spec[1]:match("^[%w-_.]+/([%w-_.]+)$")
|
||||
if not name then
|
||||
logger:log("spec", 'invalid name "%s"; must be in the format "user/package"', spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.setup ~= nil then -- spec.setup
|
||||
if type(spec.setup) ~= "function" then
|
||||
logger:log("spec", "spec.setup must be a function in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.load ~= nil then -- spec.load
|
||||
if type(spec.load) ~= "function" then
|
||||
logger:log("spec", "spec.load must be a function in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.config ~= nil then -- spec.config
|
||||
if type(spec.config) ~= "function" then
|
||||
logger:log("spec", "spec.config must be a function in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.lazy ~= nil then -- spec.lazy
|
||||
if type(spec.lazy) ~= "function" then
|
||||
logger:log("spec", "spec.lazy must be a function in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.as ~= nil then -- spec.as
|
||||
if type(spec.as) ~= "string" then
|
||||
logger:log("spec", "spec.as must be a string in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.url ~= nil then -- spec.url
|
||||
if type(spec.url) ~= "string" then
|
||||
logger:log("spec", "spec.url must be a string in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.path ~= nil then -- spec.path
|
||||
if type(spec.path) ~= "string" then
|
||||
logger:log("spec", "spec.path must be a string in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.branch ~= nil then -- spec.branch
|
||||
if type(spec.branch) ~= "string" then
|
||||
logger:log("spec", "spec.branch must be a string in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.commit ~= nil then -- spec.commit
|
||||
if type(spec.commit) ~= "string" then
|
||||
logger:log("spec", "spec.commit must be a string in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.disable ~= nil then -- spec.disable
|
||||
if type(spec.disable) ~= "boolean" then
|
||||
logger:log("spec", "spec.disable must be a boolean in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.pin ~= nil then -- spec.pin
|
||||
if type(spec.pin) ~= "boolean" then
|
||||
logger:log("spec", "spec.pin must be a boolean in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if spec.reqs ~= nil then -- spec.reqs
|
||||
local is = type(spec.reqs)
|
||||
if is ~= "table" and is ~= "string" then
|
||||
logger:log("spec", "spec.reqs must be a table or a string in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
|
||||
-- turn an id into a spec
|
||||
if (is == "string") then
|
||||
spec.reqs = { spec.reqs }
|
||||
end
|
||||
end
|
||||
|
||||
if spec.deps ~= nil then -- spec.deps
|
||||
local is = type(spec.deps)
|
||||
if is ~= "table" and is ~= "string" then
|
||||
logger:log("spec", "spec.deps must be a table or a string in %s", spec[1])
|
||||
return false
|
||||
end
|
||||
|
||||
-- turn an id into a spec
|
||||
if (is == "string") then
|
||||
spec.deps = { spec.deps }
|
||||
end
|
||||
end
|
||||
|
||||
return spec
|
||||
end
|
||||
|
||||
--- tell the parent it has a child and the child it has a parent
|
||||
---@param parent package? parent package if nil defaults to self
|
||||
---@param child package child package
|
||||
function package:link_dependency(parent, child)
|
||||
parent = parent or self
|
||||
|
||||
if not parent.dependents[child.id] then
|
||||
parent.dependents[child.id] = child
|
||||
table.insert(parent.dependents, child)
|
||||
end
|
||||
|
||||
if not child.requirements[parent.id] then
|
||||
child.requirements[parent.id] = parent
|
||||
table.insert(child.requirements, parent)
|
||||
end
|
||||
end
|
||||
|
||||
local function is_nonempty_str(s)
|
||||
return type(s) == "string" and #s ~= 0
|
||||
--- create a new package instance
|
||||
---@param spec spec|string a package spec to use for the new package
|
||||
---@param overrides spec? a package spec that is used to overried this package
|
||||
---@return package|false package an instance of the package or false on failure
|
||||
---@nodisacard
|
||||
function package:new(spec, overrides)
|
||||
overrides = overrides or {}
|
||||
|
||||
-- ensure that the spec is ok
|
||||
local new_spec = check_spec(spec)
|
||||
if new_spec == false then
|
||||
logger:log("spec", vim.inspect(spec))
|
||||
logger:log("error", "spec check failed, check DepLog")
|
||||
return false
|
||||
else
|
||||
spec = new_spec
|
||||
end
|
||||
|
||||
-- start initializing the package
|
||||
local id = spec[1]
|
||||
|
||||
local o = packages[id] or {}
|
||||
self.__index = self
|
||||
setmetatable(o, self)
|
||||
|
||||
-- if package hasn't been registered already, get the inital spec regisitered
|
||||
if not o.id then
|
||||
o.id = id
|
||||
o.enabled = true
|
||||
o.exists = false
|
||||
o.lazy = false
|
||||
o.added = false
|
||||
o.lazied = false
|
||||
o.configured = false
|
||||
o.loaded = false
|
||||
o.subtree_loaded = false
|
||||
o.on_config = {}
|
||||
o.on_setup = {}
|
||||
o.on_load = {}
|
||||
o.lazy_load = {}
|
||||
|
||||
o.requirements = {}
|
||||
o.dependents = {}
|
||||
o.perf = {}
|
||||
|
||||
packages[id] = o
|
||||
end
|
||||
|
||||
o.name = spec.as or o.name or id:match("^[%w-_.]+/([%w-_.]+)$")
|
||||
o.url = spec.url or o.url or ("https://github.com/"..id..".git")
|
||||
o.path = spec.path and vim.fs.normalize(spec.path) or spec.path
|
||||
o.branch = spec.branch or o.branch
|
||||
o.dir = base_dir.."/"..o.name
|
||||
o.commit = spec.commit
|
||||
o.pin = overrides.pin or spec.pin or o.pin
|
||||
o.enabled = not overrides.disable and not spec.disable and o.enabled
|
||||
o.lazy = spec.lazy ~= nil or o.lazy
|
||||
|
||||
-- make sure that the package exists
|
||||
o.exists = vim.fn.isdirectory(o.dir) ~= 0
|
||||
o.configured = o.exists
|
||||
|
||||
-- register all the callback functions
|
||||
if spec.config ~= nil then
|
||||
table.insert(o.on_config, spec.config)
|
||||
end
|
||||
if spec.setup ~= nil then
|
||||
table.insert(o.on_setup, spec.setup)
|
||||
end
|
||||
if spec.load ~= nil then
|
||||
table.insert(o.on_load, spec.load)
|
||||
end
|
||||
if spec.lazy ~= nil then
|
||||
table.insert(o.lazy_load, spec.lazy)
|
||||
end
|
||||
|
||||
-- if the current package isn't the root package then it depends on the root
|
||||
-- package
|
||||
if root and o ~= root then
|
||||
o:link_dependency(root, o)
|
||||
elseif not root then
|
||||
root = o
|
||||
end
|
||||
|
||||
-- link the dependencies
|
||||
if spec.reqs then
|
||||
---it is the correct type as asserted in check_spec()
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
for _, req in pairs(spec.reqs) do
|
||||
local pkg = package:new(req)
|
||||
if type(pkg) == "table" then
|
||||
o:link_dependency(pkg, o)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- and link the dependents
|
||||
if spec.deps then
|
||||
---it is the correct type as asserted in check_spec()
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
for _, dep in pairs(spec.deps) do
|
||||
local pkg = package:new(dep)
|
||||
if not pkg then
|
||||
return false
|
||||
end
|
||||
o:link_dependency(o, pkg)
|
||||
|
||||
-- if the child package is lazy loaded make sure the child package
|
||||
-- is only loaded when the parent package has finished loading
|
||||
if o.lazy then
|
||||
table.insert(o.on_load, function()
|
||||
local ok = o:loadtree(true)
|
||||
if not ok then
|
||||
logger:log("lazy",
|
||||
"failed to run loadtree for %s, some packages may not be loaded",
|
||||
o.id)
|
||||
end
|
||||
end)
|
||||
|
||||
-- tell the dep that it's gonna be lazy
|
||||
pkg.lazy = true
|
||||
table.insert(pkg.lazy_load, function(_) end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
--- Package information.
|
||||
local Package = setmetatable({
|
||||
__metatable = "Package",
|
||||
__index = {
|
||||
--- Runs all registered hooks of the given type.
|
||||
run_hooks = function(self, hook)
|
||||
local hooks = self["on_" .. hook]
|
||||
if not hooks or #hooks == 0 then
|
||||
return true
|
||||
end
|
||||
--- set the base directory for packages
|
||||
---@param _base_dir string base directory
|
||||
function package.set_base_dir(_base_dir)
|
||||
base_dir = vim.fs.normalize(_base_dir)
|
||||
end
|
||||
|
||||
local start = os.clock()
|
||||
for i = 1, #hooks do
|
||||
local ok, err = xpcall(hooks[i], debug.traceback)
|
||||
if not ok then
|
||||
return false, err
|
||||
--- get the base directory for packages
|
||||
---@return string base_dir
|
||||
---@nodiscard
|
||||
function package.get_base_dir()
|
||||
return base_dir
|
||||
end
|
||||
|
||||
--- get the root directory
|
||||
---@return package root
|
||||
---@nodiscard
|
||||
function package.get_root()
|
||||
return root
|
||||
end
|
||||
|
||||
--- get the packages in dep
|
||||
---@return package root
|
||||
---@nodiscard
|
||||
function package.get_packages()
|
||||
return packages
|
||||
end
|
||||
|
||||
--- run specified hooks on the current package
|
||||
---@param type "on_load"|"on_config"|"on_setup" which hook to run
|
||||
---@return boolean, string|nil
|
||||
function package:runhooks(type)
|
||||
local hooks = self[type]
|
||||
if #hooks == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
local start = os.clock()
|
||||
|
||||
-- chdir into the package directory to make running external commands
|
||||
-- from hooks easier.
|
||||
local last_cwd = vim.fn.getcwd()
|
||||
vim.fn.chdir(self.dir)
|
||||
|
||||
for i = 1, #hooks do
|
||||
local ok, err = pcall(hooks[i])
|
||||
if not ok then
|
||||
vim.fn.chdir(last_cwd)
|
||||
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
vim.fn.chdir(last_cwd)
|
||||
self.perf[type] = os.clock() - start
|
||||
|
||||
logger:log("hook", "triggered %d %s %s for %s", #hooks, type,
|
||||
#hooks == 1 and "hook" or "hooks", self.id)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- make sure a package has been loaded
|
||||
---@param force boolean? force lazy packages to load
|
||||
---@return boolean|table return true or false if loaded or package spec if lazy loaded
|
||||
function package:ensureadded(force)
|
||||
--- load a package
|
||||
---@param pkg package
|
||||
local function loadpkg(pkg)
|
||||
-- make sure to load the dependencies first
|
||||
for _, p in pairs(pkg.requirements) do
|
||||
if not p.loaded then
|
||||
p:ensureadded(true)
|
||||
end
|
||||
end
|
||||
|
||||
-- run setup hooks
|
||||
pkg:runhooks("on_setup")
|
||||
|
||||
-- now start loading our plugin
|
||||
local start = os.clock()
|
||||
|
||||
-- trigger the packadd for the plugin
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
local ok, err = pcall(vim.cmd, "packadd "..pkg.name)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
|
||||
pkg.added = true
|
||||
pkg.perf.pack = os.clock() - start
|
||||
logger:log("vim", "packadd completed for %s", pkg.id)
|
||||
|
||||
-- set the package to loaded
|
||||
pkg.loaded = true
|
||||
logger:log("load", "loaded %s", pkg.id)
|
||||
|
||||
-- trigger the on_load hooks
|
||||
ok, err = pkg:runhooks("on_load")
|
||||
if not ok then
|
||||
logger:log("error", "failed to load %s; reason: %s", pkg.id, err)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- make sure the package is lazy loaded if need be
|
||||
if not self.added and not self.loaded and not self.lazy or force then
|
||||
loadpkg(self)
|
||||
elseif not self.added and self.lazy then
|
||||
logger:log("lazy", "registering %d lazy hooks for %s", #self.lazy_load,
|
||||
self.id)
|
||||
self.lazied = true
|
||||
for _, load_cond in pairs(self.lazy_load) do
|
||||
-- configure the lazy loader for the user
|
||||
local l = require('lazy.utils'):new()
|
||||
if l == true then
|
||||
logger:log("lazy", "failed to get lazy utils")
|
||||
return false
|
||||
end
|
||||
l:set_load(function()
|
||||
logger:log("lazy", "triggered %d lazy hooks for %s", #self.lazy_load,
|
||||
self.id)
|
||||
loadpkg(self)
|
||||
end)
|
||||
|
||||
-- run it
|
||||
load_cond(l)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- load all packages in package tree
|
||||
---@param force boolean? force lazy packages to load
|
||||
---@return boolean boolean if tree was successfully loaded
|
||||
---@nodiscard
|
||||
function package:loadtree(force)
|
||||
-- if the package doesn't exist or isn't enabled then don't load it
|
||||
if not self.exists or not self.enabled then
|
||||
return false
|
||||
end
|
||||
|
||||
-- if the subtree is loaded then it's already loaded unless it needs forcing
|
||||
if not force and self.subtree_loaded then
|
||||
return true
|
||||
end
|
||||
|
||||
-- if the package isn't lazy check that it's requirements are loaded
|
||||
if not self.lazy then
|
||||
for _, requirement in pairs(self.requirements) do
|
||||
if not requirement.loaded and not requirement.lazy then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if the package isn't loaded and isn't lazy then it should probably be
|
||||
-- loaded
|
||||
if not self.loaded then
|
||||
local ok, err = self:ensureadded(force)
|
||||
if not ok then
|
||||
logger:log("error", "failed to load %s; reason: %s", self.id, err)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
self.subtree_loaded = true
|
||||
|
||||
-- make sure the dependants are loaded
|
||||
for _, dependant in pairs(self.dependents) do
|
||||
self.subtree_loaded = dependant:loadtree(force) and self.subtree_loaded
|
||||
end
|
||||
|
||||
return self.subtree_loaded
|
||||
end
|
||||
|
||||
--- unconfigure a packages tree
|
||||
function package:unconfiguretree()
|
||||
-- unconfigure requirements
|
||||
for _, requirement in pairs(self.requirements) do
|
||||
requirement.subtree_loaded = false
|
||||
end
|
||||
|
||||
-- unconfigure dependents
|
||||
for _, dependant in pairs(self.dependents) do
|
||||
dependant.loaded = false
|
||||
dependant.added = false
|
||||
dependant.configured = false
|
||||
|
||||
dependant.subtree_loaded = false
|
||||
end
|
||||
end
|
||||
|
||||
--- check a list of packages for any cycles
|
||||
---@param pkgs package[] list of packages
|
||||
---@return package[]|false cycle the cycle that was found or false if not found
|
||||
---@nodisacard
|
||||
function package.findcycle(pkgs)
|
||||
local index = 0
|
||||
local indexes = {}
|
||||
local lowlink = {}
|
||||
local stack = {}
|
||||
|
||||
--- use tarjan algorithm to find circular dependencies (strongly connected
|
||||
--- components)
|
||||
---@param pkg package
|
||||
local function connect(pkg)
|
||||
indexes[pkg.id], lowlink[pkg.id] = index, index
|
||||
stack[#stack + 1], stack[pkg.id] = pkg, true
|
||||
index = index + 1
|
||||
|
||||
for i = 1, #pkg.dependents do
|
||||
local dependent = pkg.dependents[i]
|
||||
|
||||
if not indexes[dependent.id] then
|
||||
local cycle = connect(dependent)
|
||||
if cycle then
|
||||
return cycle
|
||||
else
|
||||
lowlink[pkg.id] = math.min(lowlink[pkg.id], lowlink[dependent.id])
|
||||
end
|
||||
elseif stack[dependent.id] then
|
||||
lowlink[pkg.id] = math.min(lowlink[pkg.id], indexes[dependent.id])
|
||||
end
|
||||
end
|
||||
|
||||
local elapsed = os.clock() - start
|
||||
self.perf.hooks[hook] = elapsed
|
||||
if lowlink[pkg.id] == indexes[pkg.id] then
|
||||
local cycle = { pkg }
|
||||
local node
|
||||
|
||||
logger:log(
|
||||
"hook",
|
||||
"triggered %d %s %s for %s in %dms",
|
||||
#hooks,
|
||||
hook,
|
||||
#hooks == 1 and "hook" or "hooks",
|
||||
self.id,
|
||||
elapsed
|
||||
)
|
||||
repeat
|
||||
node = stack[#stack]
|
||||
stack[#stack], stack[node.id] = nil, nil
|
||||
cycle[#cycle + 1] = node
|
||||
until node == pkg
|
||||
|
||||
return true
|
||||
end,
|
||||
},
|
||||
}, {
|
||||
--- Constructs a new `Package` with the given identifier.
|
||||
__call = function(mt, id)
|
||||
local name = parse_name_from_id(id)
|
||||
return setmetatable({
|
||||
id = id,
|
||||
name = name,
|
||||
url = "https://github.com/" .. id .. ".git",
|
||||
enabled = true,
|
||||
exists = false,
|
||||
added = false,
|
||||
configured = false,
|
||||
loaded = false,
|
||||
dependencies = {},
|
||||
dependents = {},
|
||||
subtree_configured = false,
|
||||
subtree_loaded = false,
|
||||
on_setup = {},
|
||||
on_config = {},
|
||||
on_load = {},
|
||||
perf = { hooks = {} },
|
||||
}, mt)
|
||||
end,
|
||||
})
|
||||
|
||||
--- Manages a set of packages.
|
||||
local PackageStore = setmetatable({
|
||||
__metatable = "PackageStore",
|
||||
__index = {
|
||||
--- Links the given packages such that the parent must load before the child.
|
||||
link_dependency = function(self, parent, child)
|
||||
if not parent.dependents[child.id] then
|
||||
parent.dependents[child.id] = child
|
||||
parent.dependents[#parent.dependents + 1] = child
|
||||
-- a node is by definition strongly connected to itself ignore single-node
|
||||
-- components unless it explicitly specified itself as a dependency
|
||||
if #cycle > 2 or pkg.dependents[pkg.id] then
|
||||
return cycle
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not child.dependencies[parent.id] then
|
||||
child.dependencies[parent.id] = parent
|
||||
child.dependencies[#child.dependencies + 1] = parent
|
||||
-- actually check the cycle
|
||||
for _, pkg in pairs(pkgs) do
|
||||
if not indexes[package.id] then
|
||||
local cycle = connect(pkg)
|
||||
if cycle then
|
||||
return cycle
|
||||
end
|
||||
end,
|
||||
end
|
||||
end
|
||||
|
||||
--- Ensures the given package spec table is valid.
|
||||
validate_spec = function(self, spec)
|
||||
assert(spec[1] ~= nil, "package id missing from spec")
|
||||
assert(type(spec[1]) == "string", "package id must be a string")
|
||||
parse_name_from_id(spec[1])
|
||||
return false
|
||||
end
|
||||
|
||||
assert(spec.as == nil or is_nonempty_str(spec.as), "package name must be a string")
|
||||
assert(spec.url == nil or type(spec.url) == "string", "package url must be a string") -- TODO: validate url or path
|
||||
assert(spec.branch == nil or is_nonempty_str(spec.branch), "package branch must be a string")
|
||||
assert(spec.pin == nil or type(spec.pin) == "boolean", "package pin must be a boolean")
|
||||
assert(spec.disable == nil or type(spec.disable) == "boolean", "package disable must be a boolean")
|
||||
|
||||
assert(
|
||||
spec.requires == nil or type(spec.requires) == "table" or type(spec.requires) == "string",
|
||||
"package requires must be a string or table"
|
||||
)
|
||||
assert(
|
||||
spec.deps == nil or type(spec.deps) == "table" or type(spec.deps) == "string",
|
||||
"package deps must be a string or table"
|
||||
)
|
||||
|
||||
assert(spec.setup == nil or type(spec.setup) == "function", "package setup must be a function")
|
||||
assert(spec.config == nil or type(spec.config) == "function", "package config must be a function")
|
||||
assert(spec[2] == nil or type(spec[2]) == "function", "package loader must be a function")
|
||||
end,
|
||||
|
||||
--- Creates or updates a package from the given spec table, and returns that package.
|
||||
add_spec = function(self, spec, scope)
|
||||
self:validate_spec(spec)
|
||||
scope = scope or {}
|
||||
|
||||
local id = spec[1]
|
||||
local pkg = self[id]
|
||||
|
||||
if not pkg then
|
||||
pkg = Package(id)
|
||||
self[id], self[#self + 1] = pkg, pkg
|
||||
end
|
||||
|
||||
-- blend package spec with existing package info
|
||||
pkg.name = spec.as or pkg.name
|
||||
pkg.url = spec.url or pkg.url
|
||||
pkg.branch = spec.branch or pkg.branch
|
||||
pkg.pin = scope.pin or spec.pin or pkg.pin
|
||||
pkg.enabled = not scope.disable and not spec.disable and pkg.enabled
|
||||
|
||||
pkg.on_setup[#pkg.on_setup + 1] = spec.setup
|
||||
pkg.on_config[#pkg.on_config + 1] = spec.config
|
||||
pkg.on_load[#pkg.on_load + 1] = spec[2]
|
||||
|
||||
local requires = type(spec.requires) == "table" and spec.requires or { spec.requires }
|
||||
local deps = type(spec.deps) == "table" and spec.deps or { spec.deps }
|
||||
|
||||
-- recursively add specs for dependencies and dependents
|
||||
for i = 1, #requires do
|
||||
self:link_dependency(self:add_spec(requires[i], scope), pkg)
|
||||
end
|
||||
|
||||
for i = 1, #deps do
|
||||
self:link_dependency(pkg, self:add_spec(deps[i], scope))
|
||||
end
|
||||
end,
|
||||
|
||||
--- Adds the given list of specs.
|
||||
add_specs = function(self, specs, scope)
|
||||
assert(type(specs) == "table", "package list must be a table")
|
||||
assert(specs.pin == nil or type(specs.pin) == "boolean", "package list pin must be a boolean")
|
||||
assert(specs.disable == nil or type(specs.disable) == "boolean", "package list disable must be a boolean")
|
||||
|
||||
scope = scope or {}
|
||||
scope = {
|
||||
-- outer scope takes precedence over inner list's overrides
|
||||
pin = scope.pin or specs.pin,
|
||||
disable = scope.disable or specs.disable,
|
||||
}
|
||||
|
||||
-- add specs in spec list
|
||||
for i = 1, #specs do
|
||||
self:add_spec(specs[i], scope)
|
||||
end
|
||||
end,
|
||||
|
||||
--- Ensures there are no circular dependencies in this package store.
|
||||
ensure_acyclic = function(self)
|
||||
-- tarjan's strongly connected components algorithm
|
||||
local idx, indices, lowlink, stack = 0, {}, {}, {}
|
||||
|
||||
local function connect(pkg)
|
||||
indices[pkg.id], lowlink[pkg.id] = idx, idx
|
||||
stack[#stack + 1], stack[pkg.id] = pkg, true
|
||||
idx = idx + 1
|
||||
|
||||
for i = 1, #pkg.dependents do
|
||||
local dependent = pkg.dependents[i]
|
||||
|
||||
if not indices[dependent.id] then
|
||||
local cycle = connect(dependent)
|
||||
if cycle then
|
||||
return cycle
|
||||
else
|
||||
lowlink[pkg.id] = math.min(lowlink[pkg.id], lowlink[dependent.id])
|
||||
end
|
||||
elseif stack[dependent.id] then
|
||||
lowlink[pkg.id] = math.min(lowlink[pkg.id], indices[dependent.id])
|
||||
end
|
||||
end
|
||||
|
||||
if lowlink[pkg.id] == indices[pkg.id] then
|
||||
local cycle = { pkg }
|
||||
local node
|
||||
|
||||
repeat
|
||||
node = stack[#stack]
|
||||
stack[#stack], stack[node.id] = nil, nil
|
||||
cycle[#cycle + 1] = node
|
||||
until node == pkg
|
||||
|
||||
-- a node is by definition strongly connected to itself
|
||||
-- ignore single-node components unless the package explicitly specified itself as a dependency (i.e. the user is being weird)
|
||||
if #cycle > 2 or pkg.dependents[pkg.id] then
|
||||
return cycle
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #self do
|
||||
local pkg = self[i]
|
||||
|
||||
if not indices[pkg.id] then
|
||||
local cycle = connect(pkg)
|
||||
if cycle then
|
||||
-- found dependency cycle
|
||||
local names = {}
|
||||
for j = 1, #cycle do
|
||||
names[j] = cycle[j].id
|
||||
end
|
||||
error("circular dependency detected in package dependency graph: " .. table.concat(names, " -> "))
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
}, {
|
||||
--- Constructs a new `PackageStore`.
|
||||
__call = function(mt)
|
||||
-- hash part of store maps package ids to packages
|
||||
-- array part of store is a list of packages
|
||||
-- all packages in a store are unique based on their id
|
||||
return setmetatable({}, mt)
|
||||
end,
|
||||
})
|
||||
|
||||
return {
|
||||
Package = Package,
|
||||
PackageStore = PackageStore,
|
||||
}
|
||||
return package
|
||||
|
@ -1,23 +1,19 @@
|
||||
local logger = require("dep.log").global
|
||||
local proc = {}
|
||||
|
||||
--- execute a process
|
||||
---@param process string the program
|
||||
---@param args string[] the args
|
||||
---@param cwd string? the pwd
|
||||
---@param env table env
|
||||
---@param cb function callback
|
||||
function proc.exec(process, args, cwd, env, cb)
|
||||
local buffer = {}
|
||||
|
||||
local function cb_output(_, data, _)
|
||||
table.insert(buffer, table.concat(data))
|
||||
end
|
||||
local function cb_exit(job_id, exit_code, _)
|
||||
local function cb_exit(_, exit_code, _)
|
||||
local output = table.concat(buffer)
|
||||
logger:log(
|
||||
process,
|
||||
string.format(
|
||||
'Job %s ["%s"] finished with exitcode %s\n%s',
|
||||
job_id,
|
||||
table.concat(args, '", "'),
|
||||
exit_code,
|
||||
output)
|
||||
)
|
||||
cb(exit_code ~= 0, output)
|
||||
end
|
||||
table.insert(args, 1, process)
|
||||
@ -43,7 +39,7 @@ function proc.git_clone(dir, url, branch, cb)
|
||||
local args = { "clone", "--depth=1", "--recurse-submodules", "--shallow-submodules", url, dir }
|
||||
|
||||
if branch then
|
||||
args[#args + 1] = "--branch=" .. branch
|
||||
args[#args + 1] = "--branch="..branch
|
||||
end
|
||||
|
||||
proc.exec("git", args, nil, git_env, cb)
|
||||
@ -71,4 +67,62 @@ function proc.git_checkout(dir, branch, commit, cb)
|
||||
end)
|
||||
end
|
||||
|
||||
function proc.git_resolve_branch(url, branch, cb)
|
||||
-- if the branch doesn't contain a * then return the branch
|
||||
if not string.match(branch, "*") then
|
||||
cb(false, branch)
|
||||
return
|
||||
end
|
||||
|
||||
local buffer = {}
|
||||
local function cb_output(_, data, _)
|
||||
if data[1] ~= "" then
|
||||
buffer = data
|
||||
end
|
||||
end
|
||||
|
||||
vim.fn.jobstart({ "git", "ls-remote", "--tags", "--sort", "v:refname", url },
|
||||
{
|
||||
cwd = nil,
|
||||
env = git_env,
|
||||
stdin = nil,
|
||||
on_stdout = cb_output,
|
||||
on_stderr = cb_output,
|
||||
on_exit = function(_, exit_code, _)
|
||||
if exit_code ~= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- get a list of all versions
|
||||
local versions = {}
|
||||
for _, v in pairs(buffer) do
|
||||
local s, e = string.find(v, "refs/tags/.+")
|
||||
if not s or not e then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local tag = string.sub(v, s, e)
|
||||
tag = tag:gsub("refs/tags/", ""):gsub("%^{}", "")
|
||||
|
||||
table.insert(versions, tag)
|
||||
::continue::
|
||||
end
|
||||
|
||||
-- match the chosen version against all versions
|
||||
for i = #versions, 1, -1 do
|
||||
if branch == "*" then
|
||||
cb(false, versions[i])
|
||||
return
|
||||
else
|
||||
local r = string.match(versions[i], branch)
|
||||
if r then
|
||||
cb(false, r)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
return proc
|
||||
|
12
lua/dep2.lua
12
lua/dep2.lua
@ -1,12 +0,0 @@
|
||||
--
|
||||
-- Copyright (c) 2022 chiya.dev
|
||||
--
|
||||
-- Use of this source code is governed by the MIT License
|
||||
-- which can be found in the LICENSE file and at:
|
||||
--
|
||||
-- https://chiya.dev/licenses/mit.txt
|
||||
--
|
||||
local logger = require("dep.log").global
|
||||
local store = require("dep.package").PackageStore()
|
||||
|
||||
-- placeholder for refactoring
|
116
lua/lazy/utils.lua
Normal file
116
lua/lazy/utils.lua
Normal file
@ -0,0 +1,116 @@
|
||||
local logger = require('dep.log')
|
||||
|
||||
---@class lazy
|
||||
---@field load function the function to load the plugin
|
||||
---@field command_ids table the commands that have been registered
|
||||
---@field auto_ids table the auto commands that have been registered
|
||||
---@field keybind_ids table the keybinds that have been registered
|
||||
local lazy = {}
|
||||
|
||||
--- create a new instance of lazy
|
||||
---@return lazy
|
||||
function lazy:new()
|
||||
local o = {}
|
||||
|
||||
setmetatable(o, self)
|
||||
o.command_ids = {}
|
||||
o.auto_ids = {}
|
||||
o.keybind_ids = {}
|
||||
|
||||
self.__index = self
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
--- set the loading function
|
||||
---@param load function the loading function
|
||||
function lazy:set_load(load)
|
||||
self.load = load
|
||||
end
|
||||
|
||||
--- get the configured load function
|
||||
---@return function load function
|
||||
function lazy:get_load()
|
||||
return self.load
|
||||
end
|
||||
|
||||
--- create a usercommand which will trigger the plugin to load
|
||||
---@param name string the name of the command
|
||||
---@param opts vim.api.keyset.user_command? options
|
||||
function lazy:cmd(name, opts)
|
||||
opts = opts or {}
|
||||
vim.api.nvim_create_user_command(name, opts['callback'] or function()
|
||||
self:cleanup()
|
||||
end, opts)
|
||||
|
||||
table.insert(self.command_ids, name)
|
||||
end
|
||||
|
||||
--- user an auto command which will trigger the plugin to load
|
||||
---@param event string the event to trigger on
|
||||
---@param opts vim.api.keyset.create_autocmd? options
|
||||
function lazy:auto(event, opts)
|
||||
opts = opts or {}
|
||||
|
||||
opts['callback'] = opts['callback'] or function()
|
||||
self:cleanup()
|
||||
end
|
||||
|
||||
-- create the auto command and save it
|
||||
table.insert(self.auto_ids, vim.api.nvim_create_autocmd(event, opts))
|
||||
end
|
||||
|
||||
--- create a keybind which will trigger the plugin to load
|
||||
---@param mode string the mode to trigger in
|
||||
---@param bind string the binding to use
|
||||
---@param opts vim.keymap.set.Opts? options
|
||||
function lazy:keymap(mode, bind, opts)
|
||||
opts = opts or {}
|
||||
|
||||
-- move the rerun arg to a seperate variable because keymap.set doesn't like
|
||||
-- options it doesn't know of
|
||||
local rerun = opts['rerun'] or true
|
||||
opts['rerun'] = nil
|
||||
|
||||
vim.keymap.set(mode, bind, opts['callback'] or function()
|
||||
-- register keymap unload
|
||||
self:cleanup()
|
||||
|
||||
-- call the keymap after the user has mapped it
|
||||
if rerun then
|
||||
local keys = vim.api.nvim_replace_termcodes(bind, true, false, true)
|
||||
vim.api.nvim_input(keys)
|
||||
end
|
||||
end, opts)
|
||||
|
||||
table.insert(self.keybind_ids, { ['mode'] = mode, ['bind'] = bind })
|
||||
end
|
||||
|
||||
--- cleanup all the callbacks, and load the plugin
|
||||
function lazy:cleanup()
|
||||
-- cleanup user commands
|
||||
for _, command_id in pairs(self.command_ids) do
|
||||
local ok, err = pcall(vim.api.nvim_del_user_command, command_id)
|
||||
if not ok then
|
||||
logger:log("lazy", err or "failed to delete user command")
|
||||
end
|
||||
end
|
||||
-- cleanup auto commands
|
||||
for _, auto_id in pairs(self.auto_ids) do
|
||||
local ok, err = pcall(vim.api.nvim_del_autocmd, auto_id)
|
||||
if not ok then
|
||||
logger:log("lazy", err or "failed to delete auto command")
|
||||
end
|
||||
end
|
||||
-- cleanup keymaps
|
||||
for _, keybind_id in pairs(self.keybind_ids) do
|
||||
local ok, err = pcall(vim.keymap.del, keybind_id.mode, keybind_id.bind, {})
|
||||
if not ok then
|
||||
logger:log("lazy", err or "failed to delete keymap")
|
||||
end
|
||||
end
|
||||
-- load the plugin
|
||||
self:load()
|
||||
end
|
||||
|
||||
return lazy
|
@ -1,2 +0,0 @@
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
Reference in New Issue
Block a user