Compare commits

..

10 Commits

Author SHA1 Message Date
5a395de735 add arbitrary tag selection with *s 2024-12-23 21:46:44 -05:00
6d6568ecfd Merge branch 'master' into dev 2024-12-23 21:42:38 -05:00
443a091e3e fix: accidentally jumps to FETCH_HEAD 2024-11-19 12:53:06 -06:00
6259250120 fix: --unshallow errors if the repo is already unshallow
instead we use --depth=2147483647 because as noted by the docs...

  The special depth 2147483647 (or 0x7fffffff, the largest positive number a
  signed 32-bit integer can contain) means infinite depth.

  https://git-scm.com/docs/shallow
2024-11-19 12:41:54 -06:00
25372aea36 add ability to specifiy commit ref 2024-11-19 12:36:55 -06:00
30e7e05771 make sure the load function is called on dep reloading 2024-07-25 20:01:23 -04:00
d7a08ca820 actually fix it this time 2024-07-25 10:36:27 -04:00
d141c762c1 whoops 2024-07-25 10:27:12 -04:00
3d20ae8d2a pin the correct repo to the top of the plugin list 2024-07-25 10:25:36 -04:00
d6460d53ed notify user that this is a development version 2023-04-29 12:18:06 -04:00
17 changed files with 1317 additions and 1681 deletions

View File

@ -1,7 +1,7 @@
MIT License MIT License
Copyright (c) 2023 squibid (c) 2021 chiya.dev
Copyright (c) 2021-2023 chiya.dev (c) 2024 squibid
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

245
README.md
View File

@ -1,29 +1,27 @@
# dep # dep
[![License](https://img.shields.io/github/license/chiyadev/dep)](LICENSE)
[![Maintainer](https://img.shields.io/badge/maintainer-luaneko-pink)][4]
[![Issues](https://img.shields.io/github/issues/chiyadev/dep.svg)][8]
[![Contributors](https://img.shields.io/github/contributors/chiyadev/dep.svg)][9]
> This readme is a work in progress. > This readme is a work in progress.
A versatile, declarative and correct [neovim][2] package manager in [Lua][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 Originally written for personal use by [luaneko][4].
general use.
What does that mean? What does that mean?
1. `versatile` - packages can be declared in any Lua file in any order of your 1. `versatile` - packages can be declared in any Lua file in any order of your liking.
liking.
2. `declarative` - packages are declared using simple Lua tables. 2. `declarative` - packages are declared using simple Lua tables.
3. `correct` - packages are always loaded in a correct and consistent order. 3. `correct` - packages are always loaded in a correct and consistent order.
In addition to the above dep has been built to be completely in control of you, See also luaneko's [neovim-configs][10] for an example of how dep can be used in practice.
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 ## Requirements
- [Neovim][2] 0.8+ - [Neovim][2] 0.6+
- [Git][6] 2.13+ - [Git][5]
## Setup ## Setup
@ -31,11 +29,11 @@ practice.
```lua ```lua
-- ~/.config/nvim/lua/bootstrap.lua: -- ~/.config/nvim/lua/bootstrap.lua:
-- automatically install `squibid/dep` on startup -- automatically install `chiyadev/dep` on startup
local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep" local path = vim.fn.stdpath("data") .. "/site/pack/deps/opt/dep"
if vim.fn.empty(vim.fn.glob(path)) > 0 then if vim.fn.empty(vim.fn.glob(path)) > 0 then
vim.fn.system({ "git", "clone", "--depth=1", "https://git.squi.bid/dep", path }) vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/chiyadev/dep", path })
end end
vim.cmd("packadd dep") vim.cmd("packadd dep")
@ -56,7 +54,9 @@ require "dep" {
cleans removed packages and reloads packages as necessary. cleans removed packages and reloads packages as necessary.
- `:DepClean` - cleans removed packages. - `:DepClean` - cleans removed packages.
- `:DepReload` - reloads all packages. - `:DepReload` - reloads all packages.
- `:DepList` - prints the package list, performance metrics and dependency graphs.
- `:DepLog` - opens the log file. - `:DepLog` - opens the log file.
- `:DepConfig` - opens the file that called dep, for convenience.
## Package specification ## Package specification
@ -68,25 +68,21 @@ A package must be declared in the following format.
-- This is the only required field; all other fields are optional. -- This is the only required field; all other fields are optional.
"user/package", "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. -- [function] Code to run before the package is loaded into neovim.
setup = function() setup = function()
vim.g.package_config = ... vim.g.package_config = ...
end, 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. -- [function] Code to run after the package is installed or updated.
config = function() config = function()
os.execute(...) os.execute(...)
end, end,
-- [function] Code used to determine when the package should be loaded.
lazy = function(load)
end,
-- [string] Overrides the short name of the package. -- [string] Overrides the short name of the package.
-- Defaults to a substring of the full name after '/'. -- Defaults to a substring of the full name after '/'.
as = "custom_package", as = "custom_package",
@ -95,10 +91,6 @@ A package must be declared in the following format.
-- Defaults to "https://github.com/{full_name}.git". -- Defaults to "https://github.com/{full_name}.git".
url = "https://git.chiya.dev/user/package.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. -- [string] Overrides the name of the branch to clone.
-- Defaults to whatever the remote configured as their HEAD, which is usually "master". -- Defaults to whatever the remote configured as their HEAD, which is usually "master".
branch = "develop", branch = "develop",
@ -113,9 +105,9 @@ A package must be declared in the following format.
-- [boolean] Prevents the package from being updated. -- [boolean] Prevents the package from being updated.
pin = true, pin = true,
-- [string|array] Specifies requirements that must be loaded before the package. -- [string|array] Specifies dependencies that must be loaded before the package.
-- If given a string, it is wrapped into an array. -- If given a string, it is wrapped into an array.
reqs = {...}, requires = {...},
-- [string|array] Specifies dependents that must be loaded after the package. -- [string|array] Specifies dependents that must be loaded after the package.
-- If given a string, it is wrapped into an array. -- If given a string, it is wrapped into an array.
@ -141,7 +133,7 @@ combined into one. This is useful when declaring dependencies, which is explored
require "dep" { require "dep" {
{ {
"user/package", "user/package",
reqs = "user/dependency", requires = "user/dependency",
disabled = true, disabled = true,
config = function() config = function()
print "my config hook" print "my config hook"
@ -162,7 +154,7 @@ require "dep" {
require "dep" { require "dep" {
{ {
"user/package", "user/package",
reqs = { "user/dependency", "user/another_dependency" }, requires = { "user/dependency", "user/another_dependency" },
deps = "user/dependent", deps = "user/dependent",
disabled = true, disabled = true,
config = function() config = function()
@ -183,10 +175,10 @@ they are combined into one just like normal package specifications.
require "dep" { require "dep" {
{ {
"user/package", "user/package",
reqs = { requires = {
{ {
"user/dependency1", "user/dependency1",
reqs = "user/dependency2" requires = "user/dependency2"
} }
} }
} }
@ -209,7 +201,7 @@ require "dep" {
require "dep" { require "dep" {
{ {
"user/dependency1", "user/dependency1",
reqs = "user/dependency2", requires = "user/dependency2",
deps = "user/package" deps = "user/package"
} }
} }
@ -218,11 +210,11 @@ require "dep" {
require "dep" { require "dep" {
{ {
"user/dependency1", "user/dependency1",
reqs = "user/dependency2" requires = "user/dependency2"
}, },
{ {
"user/package", "user/package",
reqs = "user/dependency1" requires = "user/dependency1"
} }
} }
@ -249,11 +241,11 @@ instead of hanging or crashing.
require "dep" { require "dep" {
{ {
"user/package1", "user/package1",
reqs = "user/package2" requires = "user/package2"
}, },
{ {
"user/package2", "user/package2",
reqs = "user/package1" requires = "user/package1"
} }
} }
``` ```
@ -269,12 +261,12 @@ require "dep" {
{ {
"user/package1", "user/package1",
disabled = true, -- implied disabled = true, -- implied
reqs = "user/dependency" requires = "user/dependency"
}, },
{ {
"user/package2", "user/package2",
disabled = true, -- implied disabled = true, -- implied
reqs = "user/dependency" requires = "user/dependency"
} }
} }
``` ```
@ -285,165 +277,20 @@ If a dependency fails to load for some reason, all of its dependents are guarant
require "dep" { require "dep" {
{ {
"user/problematic", "user/problematic",
load = function() function()
error("bad hook") error("bad hook")
end end
}, },
{ {
"user/dependent", "user/dependent",
requires = "user/problematic", requires = "user/problematic",
load = function() function()
print "unreachable" print "unreachable"
end 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 ## Miscellaneous configuration
dep accepts configuration parameters as named fields in the package list. dep accepts configuration parameters as named fields in the package list.
@ -456,14 +303,10 @@ require "dep" {
-- "always": synchronize all packages on startup -- "always": synchronize all packages on startup
sync = "new", sync = "new",
-- [array] Specifies the modules to load package specifications from. -- [function] Callback when dep is (re)loaded
-- Defaults to an empty table. -- if a table is returned it will be read as a table of config specs
-- Items can be either an array of package specifications, load = function()
-- or a string that indicates the name of the module from which the array of package specifications is loaded. end
modules = {
-- [string] Prefix string to prepend to all module names.
prefix = "",
},
-- list of package specs... -- list of package specs...
} }
@ -477,9 +320,9 @@ dep is licensed under the [MIT License](LICENSE).
[2]: https://neovim.io/ [2]: https://neovim.io/
[3]: https://www.lua.org/ [3]: https://www.lua.org/
[4]: https://github.com/luaneko [4]: https://github.com/luaneko
[5]: https://squi.bid [5]: https://git-scm.com/
[6]: https://git-scm.com/ [6]: https://github.com/nvim-telescope/telescope.nvim
[7]: https://github.com/nvim-telescope/telescope.nvim [7]: https://github.com/tpope/vim-fugitive
[8]: https://github.com/tpope/vim-fugitive [8]: https://GitHub.com/chiyadev/dep/issues
[9]: https://github.com/lewis6991/gitsigns.nvim [9]: https://github.com/chiyadev/dep/graphs/contributors
[10]: https://git.squi.bid/nvim [10]: https://github.com/luaneko/neovim-config

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
-- TODO: actually use this (ideally make a view that shows startuptime and
-- which plugins are currently loaded)
-- performance logging
---@class bench
---@field perf number[] list of all perfs
local bench = {}
local b
function bench:setup()
local o = {}
self = {}
self.__index = self
setmetatable(o, self)
o.perf = {}
o.inited = true
b = o
end
--- benchmark a peice of code
---@param name string the name of the benchmark
---@param f function the code to benchmark
---@vararg any args for f
---@return any ret the result of f
function bench.mark(name, f, ...)
local start = os.clock()
local ret = f(...)
b.perf[name] = os.clock() - start
return ret
end
return bench

View File

@ -1,78 +0,0 @@
local h = require('dep.helpers')
local logger = require('dep.log')
local fs = {}
--- abstract away fs:link to make calling more intuitive
---@param package package package to update
---@param cb function callback on success
function fs:sync(package, cb)
if not package.exists then
fs:link(package, cb)
end
end
--- create a symlink to a local package
---@param package package package to link
---@param cb function callback on success
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
--- clean out old packages
---@param package package any package
function fs:clean(package)
h.uv.fs_scandir(
package.get_base_dir(),
vim.schedule_wrap(function(err, handle)
if err then
logger:log("error", "failed to clean; reason: %s", err)
else
local queue = {}
while handle do
local name = h.uv.fs_scandir_next(handle)
if name and name ~= package.get_root().name then
queue[name] = package.get_base_dir().."/"..name
elseif name == package.get_root().name then
-- we need to ensure that there is no chance of nuking dep
goto continue
elseif name == nil then
break
else
-- if there's a single error bail out
logger:log("error", "failed to run clean uv.fs_scandir_next failed")
return
end
::continue::
end
-- keep packages that still exist
for _, pkg in pairs(package.get_packages()) do
queue[pkg.name] = nil
end
-- start deleting all of the packages which are chosen for deletion
for name, dir in pairs(queue) do
local co = coroutine.create(function()
local ok = vim.fn.delete(dir, "rf")
if ok then
logger:log("clean", "deleted %s", name)
else
logger:log("error", "failed to delete %s", name)
end
end)
coroutine.resume(co)
end
end
end)
)
end
return fs

View File

@ -1,161 +0,0 @@
-- 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
proc.git_rev_parse(package.dir, "HEAD", function(err, before)
if err then
log_err(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

View File

@ -1,4 +0,0 @@
return {
-- vim.loop was depricated in nvim 0.10
uv = vim.uv or vim.loop
}

View File

@ -1,30 +1,23 @@
local h = require('dep.helpers') --
-- 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 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() local function default_log_path()
-- create cache directory and chmod it if it doesn't already exist -- ensure cache directory exists (#5)
local path = vim.fn.stdpath("cache") local path = vim.fn.stdpath("cache")
if not h.uv.fs_stat(path) then if not vim.loop.fs_stat(path) then
h.uv.fs_mkdir(path, 0x1ff) -- 0777 vim.loop.fs_mkdir(path, 0x1ff) -- 0777
end end
return vim.fs.normalize(path).."/dep.log" return path .. "/dep.log"
end end
--- attempt to format a string
---@vararg string formating args
local function try_format(...) local function try_format(...)
local ok, s = pcall(string.format, ...) local ok, s = pcall(string.format, ...)
if ok then if ok then
@ -32,71 +25,85 @@ local function try_format(...)
end end
end end
--- setup all logging stuff --- Writes logs to a file and prints pretty status messages.
---@param path string|nil optional alternative path for the log file local Logger = setmetatable({
---@return table __metatable = "Logger",
function logger:setup(path) __index = {
logger.path = path or default_log_path() --- Prints a message associated with a stage.
local pipe log = function(self, stage, message, ...)
-- calling function
local source = debug.getinfo(2, "Sl").short_src
logger.handle = assert(h.uv.fs_open(logger.path, "w", 0x1a4)) -- 0644 -- format or stringify message
pipe = h.uv.new_pipe() if type(message) == "string" then
pipe:open(logger.handle) message = try_format(message, ...) or message
else
return pipe message = vim.inspect(message)
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
end
-- write to the pipe if it's open -- print and write must be done on the main event loop
if logger.pipe then vim.schedule(function()
logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"), if not self.silent then
source.short_src:gsub('.*%/', ''), source.currentline, message)) if stage == "error" then
end vim.api.nvim_err_writeln(string.format("[dep] %s", message))
end) elseif self.stage_colors[stage] then
end vim.api.nvim_echo({
{ "[dep]", "Identifier" },
{ " " },
{ message, self.stage_colors[stage] },
}, true, {})
end
end
--- cleanup all logging stuff if self.pipe then
---@param pipe table? pipe self.pipe:write(string.format("[%s] %s: %s\n", os.date(), source, message))
---@param handle table? handle end
function logger:cleanup(pipe, handle) end)
if pipe then end,
pipe:close()
pipe = nil
end
if handle then
h.uv.fs_close(logger.handle)
handle = nil
end
end
return logger --- 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(),
}

View File

@ -1,39 +0,0 @@
local module = require("dep.modules.module")
---@class modules
---@field modules module[] all modules in dep
local modules = {}
--- Initialize all the modules
---@param self table?
---@param speclist table
---@param overrides spec? overrides
---@return modules modules manager
---@nodisacard
function modules:setup(speclist, overrides)
overrides = overrides or {}
local o = {}
self = {}
self.__index = self
setmetatable(o, self)
-- create a list of modules
o.modules = {}
-- loop through all modules and initialize them
for _, modpath in ipairs(speclist.modules) do
local mod = module.new(
nil,
modpath,
speclist.modules.prefix,
overrides
)
table.insert(o.modules, mod)
end
return self
end
return modules

View File

@ -1,55 +0,0 @@
local spec_man = require("dep.spec")
local packager = require("dep.package")
---@class module
---@field name string name of the module
---@field path string path to the module
---@field mod table the module
local module = {}
--- Initialize a module
---@param self table?
---@param modpath string path to the module
---@param prefix string? the prefix to all modules
---@param overrides spec? a module override
---@return module|false module false on failure to load module
---@nodiscard
function module:new(modpath, prefix, overrides)
local ok, err
local o = {}
self = {}
self.__index = self
setmetatable(o, self)
o.name = "<unnamed module>"
if type(modpath) == "string" then
if prefix ~= nil then
if prefix:sub(#prefix) ~= "." and modpath:sub(1, 2) ~= "." then
modpath = "."..modpath
end
o.path = prefix..modpath
else
o.path = modpath
end
o.name = modpath
ok, o.mod = pcall(require, o.path)
if not ok then
return false
end
end
o.name = o.mod.name or o.name
-- allow a module to be a spec
if spec_man.check(o.mod, true) ~= false then
o.mod = { o.mod }
end
ok, err = pcall(packager.register_speclist, o.mod, overrides)
if not ok then
error(string.format("%s <- %s", err, o.name))
end
return self
end
return module

View File

@ -1,494 +1,258 @@
local logger = require('dep.log') --
local spec_man = require("dep.spec") -- Copyright (c) 2022 chiya.dev
local bench = require("dep.bench") --
-- 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
---@class package local function parse_name_from_id(id)
---@field id string id of the package local name = id:match("^[%w-_.]+/([%w-_.]+)$")
---@field enabled boolean whether it's going to be used if name then
---@field exists boolean if the package exists on the filesystem return name
---@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 = {}
--- 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
--- 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 = spec_man.check(spec_man.correct_spec(spec))
if new_spec == false then
logger:log("spec", vim.inspect(spec))
logger:log("error", "spec check failed, check DepLog")
return false
else else
spec = new_spec error(string.format('invalid package name "%s"; must be in the format "user/package"', id))
end end
end
-- start initializing the package local function is_nonempty_str(s)
local id = spec[1] return type(s) == "string" and #s ~= 0
end
local o = packages[id] or {} --- Package information.
self.__index = self local Package = setmetatable({
setmetatable(o, self) __metatable = "Package",
__index = {
-- if package hasn't been registered already, get the inital spec regisitered --- Runs all registered hooks of the given type.
if not o.id then run_hooks = function(self, hook)
o.id = id local hooks = self["on_" .. hook]
o.enabled = true if not hooks or #hooks == 0 then
o.exists = false return true
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 spec_man.get_name(spec)
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
end
-- and link the dependents local start = os.clock()
if spec.deps then for i = 1, #hooks do
---it is the correct type as asserted in check_spec() local ok, err = xpcall(hooks[i], debug.traceback)
---@diagnostic disable-next-line: param-type-mismatch if not ok then
for _, dep in pairs(spec.deps) do return false, err
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
--- 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
--- 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[] packages
---@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 end
elseif stack[dependent.id] then
lowlink[pkg.id] = math.min(lowlink[pkg.id], indexes[dependent.id])
end end
end
if lowlink[pkg.id] == indexes[pkg.id] then local elapsed = os.clock() - start
local cycle = { pkg } self.perf.hooks[hook] = elapsed
local node
repeat logger:log(
node = stack[#stack] "hook",
stack[#stack], stack[node.id] = nil, nil "triggered %d %s %s for %s in %dms",
cycle[#cycle + 1] = node #hooks,
until node == pkg hook,
#hooks == 1 and "hook" or "hooks",
self.id,
elapsed
)
-- a node is by definition strongly connected to itself ignore single-node return true
-- components unless it explicitly specified itself as a dependency end,
if #cycle > 2 or pkg.dependents[pkg.id] then },
return cycle }, {
--- 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
end end
end
end
-- actually check the cycle if not child.dependencies[parent.id] then
for _, pkg in pairs(pkgs) do child.dependencies[parent.id] = parent
if not indexes[package.id] then child.dependencies[#child.dependencies + 1] = parent
local cycle = connect(pkg)
if cycle then
return cycle
end end
end end,
end
return false --- Ensures the given package spec table is valid.
end 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])
--- recurse over all packages and register them assert(spec.as == nil or is_nonempty_str(spec.as), "package name must be a string")
---@param speclist speclist table of specs assert(spec.url == nil or type(spec.url) == "string", "package url must be a string") -- TODO: validate url or path
---@param overrides spec? a package spec that is used to override options assert(spec.branch == nil or is_nonempty_str(spec.branch), "package branch must be a string")
function package.register_speclist(speclist, overrides) assert(spec.pin == nil or type(spec.pin) == "boolean", "package pin must be a boolean")
overrides = overrides or {} assert(spec.disable == nil or type(spec.disable) == "boolean", "package disable must be a boolean")
-- recurse the packages assert(
local over = overrides spec.requires == nil or type(spec.requires) == "table" or type(spec.requires) == "string",
for _, spec in ipairs(speclist) do "package requires must be a string or table"
-- make sure the overrides override and take into account the packages spec )
---@diagnostic disable-next-line: missing-fields assert(
over = { spec.deps == nil or type(spec.deps) == "table" or type(spec.deps) == "string",
pin = overrides.pin or spec.pin, "package deps must be a string or table"
disable = overrides.disable or spec.disable )
}
-- While a package can fail to load we just don't care, it will work itself assert(spec.setup == nil or type(spec.setup) == "function", "package setup must be a function")
-- out. The goal is to make sure every plugin that can load does load, not assert(spec.config == nil or type(spec.config) == "function", "package config must be a function")
-- keep working plugins from loading because an unrelated one doesn't load. assert(spec[2] == nil or type(spec[2]) == "function", "package loader must be a function")
package:new(spec, over) end,
end
end
--- reload the package --- Creates or updates a package from the given spec table, and returns that package.
---@param self package the package to reload add_spec = function(self, spec, scope)
---@param force boolean? force all packages to load self:validate_spec(spec)
function package:reload(force) scope = scope or {}
local reloaded = self:loadtree(force)
if reloaded then local id = spec[1]
local ok, err local pkg = self[id]
-- TODO: make a benchmark function
bench.mark("reload", function()
ok, err = pcall(vim.cmd,
[[
silent! helptags ALL
silent! UpdateRemotePlugins
]])
end)
if not ok then if not pkg then
logger:log("error", pkg = Package(id)
"failed to reload helptags and remote plugins; reason: %s", err) self[id], self[#self + 1] = pkg, pkg
end end
end
end
return package -- 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,
}

View File

@ -1,19 +1,23 @@
local logger = require("dep.log").global
local proc = {} 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) function proc.exec(process, args, cwd, env, cb)
local buffer = {} local buffer = {}
local function cb_output(_, data, _) local function cb_output(_, data, _)
table.insert(buffer, table.concat(data)) table.insert(buffer, table.concat(data))
end end
local function cb_exit(_, exit_code, _) local function cb_exit(job_id, exit_code, _)
local output = table.concat(buffer) 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) cb(exit_code ~= 0, output)
end end
table.insert(args, 1, process) table.insert(args, 1, process)
@ -39,7 +43,7 @@ function proc.git_clone(dir, url, branch, cb)
local args = { "clone", "--depth=1", "--recurse-submodules", "--shallow-submodules", url, dir } local args = { "clone", "--depth=1", "--recurse-submodules", "--shallow-submodules", url, dir }
if branch then if branch then
args[#args + 1] = "--branch="..branch args[#args + 1] = "--branch=" .. branch
end end
proc.exec("git", args, nil, git_env, cb) proc.exec("git", args, nil, git_env, cb)
@ -68,62 +72,65 @@ function proc.git_checkout(dir, branch, commit, cb)
end end
function proc.git_resolve_branch(url, branch, cb) function proc.git_resolve_branch(url, branch, cb)
-- if the branch doesn't contain a * then return the branch if string.match(branch or "", "*") ~= "*" then
if not string.match(branch, "*") then
cb(false, branch) cb(false, branch)
return return
end end
local buffer = {} local buffer = {}
local function cb_output(_, data, _) local function cb_output(_, data, _)
if data[1] ~= "" then if data[1] ~= "" then
buffer = data buffer = data
end end
end end
vim.fn.jobstart({ "git", "ls-remote", "--tags", "--sort", "v:refname", url }, vim.fn.jobstart({
{ "git",
"ls-remote",
"--tags",
"--sort",
"v:refname",
url
}, {
cwd = nil, cwd = nil,
env = git_env, env = { GIT_TERMINAL_PROMPT = 0 },
stdin = nil, stdin = nil,
on_stdout = cb_output, on_stdout = cb_output,
on_stderr = cb_output, on_stderr = cb_output,
on_exit = function(_, exit_code, _) on_exit = function(_, exit_code, _)
if exit_code ~= 0 then if exit_code == 0 then
return -- get a list of all versions
end 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
-- get a list of all versions local tag = string.sub(v, s, e)
local versions = {} tag = string.gsub(tag, "refs/tags/", "")
for _, v in pairs(buffer) do tag = string.gsub(tag, "%^{}", "")
local s, e = string.find(v, "refs/tags/.+")
if not s or not e then table.insert(versions, tag)
goto continue ::continue::
end end
local tag = string.sub(v, s, e) -- match the chosen version against all versions
tag = tag:gsub("refs/tags/", ""):gsub("%^{}", "") for i = #versions, 1, -1 do
if branch == "*" then
table.insert(versions, tag) cb(false, versions[i])
::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 return
else
local r = string.match(versions[i], branch)
if r then
cb(false, r)
return
end
end end
end end
end end
end end
} })
)
end end
return proc return proc

View File

@ -1,209 +0,0 @@
local logger = require("dep.log")
---@class specmodules
---@field prefix string prefix to prepend to the modules
---@field [integer] string list of all modules to load
---@class speclist
---@field modules specmodules a list of modules
---@field [integer] spec a spec
---@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
local spec = {}
--- check if a string seems to be a url
---@param url string the "url" to check
---@return boolean is_url
local function is_url(url)
if url:sub(1, 8) == "https://" or
url:sub(1, 7) == "http://" then
return true
end
return false
end
--- get the proper name of a spec
---@return string spec.name
function spec:get_name()
return self[1]:match("^[%w-_.]+/([%w-_.]+)$")
end
--- attempt to correct a spec
---@param self table|string spec to check
---@return spec spec
function spec:correct_spec()
if type(self) == "string" then
return { self }
elseif type(self) == "table" then
repeat
if type(self[1]) ~= "string" then
self = self[1]
elseif self[1] == nil then
break
end
until type(self[1]) == "string"
end
return self
end
-- store the logger temporarily to prevent any logs from being printed when
-- being run in silent mode
local __logger
--- check a spec to see if it's correct
---@param self table spec to check
---@param silent boolean? should the checker report errors
---@return spec|false spec if the spec is ok or false
function spec:check(silent)
if silent == true then
__logger = logger
logger = { log = function() end }
end
-- make sure all the data is correct
do -- spec[1]
if type(self[1]) ~= "string" then
logger:log("spec", "spec[1] must be a string")
return false
end
local name = spec.get_name(self)
if not name then
logger:log("spec", 'invalid name "%s"; must be in the format "user/package"', self[1])
return false
end
end
if self.setup ~= nil then -- spec.setup
if type(self.setup) ~= "function" then
logger:log("spec", "spec.setup must be a function in %s", self[1])
return false
end
end
if self.load ~= nil then -- spec.load
if type(self.load) ~= "function" then
logger:log("spec", "spec.load must be a function in %s", self[1])
return false
end
end
if self.config ~= nil then -- spec.config
if type(self.config) ~= "function" then
logger:log("spec", "spec.config must be a function in %s", self[1])
return false
end
end
if self.lazy ~= nil then -- spec.lazy
if type(self.lazy) ~= "function" then
logger:log("spec", "spec.lazy must be a function in %s", self[1])
return false
end
end
if self.as ~= nil then -- spec.as
if type(self.as) ~= "string" then
logger:log("spec", "spec.as must be a string in %s", self[1])
return false
end
end
if self.url ~= nil then -- spec.url
if type(self.url) ~= "string" then
logger:log("spec", "spec.url must be a string in %s", self[1])
return false
elseif not is_url(self.url) then -- more strict checking on urls
logger:log("spec", "spec.url must be a properly formatted url in %s",
self[1])
return false
end
end
if self.path ~= nil then -- spec.path
if type(self.path) ~= "string" then
logger:log("spec", "spec.path must be a string in %s", self[1])
return false
elseif not vim.fn.isdirectory(self.path) then
logger:log("spec", "spec.path must be a valid directory in %s", self[1])
return false
end
end
if self.branch ~= nil then -- spec.branch
if type(self.branch) ~= "string" then
logger:log("spec", "spec.branch must be a string in %s", self[1])
return false
end
end
if self.commit ~= nil then -- spec.commit
if type(self.commit) ~= "string" then
logger:log("spec", "spec.commit must be a string in %s", self[1])
return false
end
end
if self.disable ~= nil then -- spec.disable
if type(self.disable) ~= "boolean" then
logger:log("spec", "spec.disable must be a boolean in %s", self[1])
return false
end
end
if self.pin ~= nil then -- spec.pin
if type(self.pin) ~= "boolean" then
logger:log("spec", "spec.pin must be a boolean in %s", self[1])
return false
end
end
if self.reqs ~= nil then -- spec.reqs
local is = type(self.reqs)
if is ~= "table" and is ~= "string" then
logger:log("spec", "spec.reqs must be a table or a string in %s", self[1])
return false
end
-- turn an id into a spec
if (is == "string") then
self.reqs = { self.reqs }
end
end
if self.deps ~= nil then -- spec.deps
local is = type(self.deps)
if is ~= "table" and is ~= "string" then
logger:log("spec", "spec.deps must be a table or a string in %s", self[1])
return false
end
-- turn an id into a spec
if (is == "string") then
self.deps = { self.deps }
end
end
if silent == true then
logger = __logger
end
return self
end
return spec

12
lua/dep2.lua Normal file
View File

@ -0,0 +1,12 @@
--
-- 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

View File

@ -1,43 +0,0 @@
--- shorthands for when you only need to define one callback
local short = {}
--- 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
---@return function callback lazy setup
function short:cmd(name, opts)
return function(load)
load:cmd(name, opts)
end
end
--- create 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
---@return function callback lazy setup
function short:auto(event, opts)
return function(load)
load:auto(event, opts)
end
end
--- create an auto command which will trigger on filetype
---@param filetype string filetype to register the auto on
function short:ft(filetype)
return function(load)
load:ft(filetype)
end
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
---@return function callback lazy setup
function short:keymap(mode, bind, opts)
return function(load)
load:keymap(mode, bind, opts)
end
end
return short

View File

@ -1,124 +0,0 @@
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
--- create 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 an auto command which will trigger on filetype
---@param filetype string filetype to register the auto on
function lazy:ft(filetype)
lazy:auto("FileType", {
pattern = filetype
})
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

2
stylua.toml Executable file
View File

@ -0,0 +1,2 @@
indent_type = "Spaces"
indent_width = 2