Compare commits

...
Sign in to create a new pull request.

74 commits
master ... pack

Author SHA1 Message Date
e23bd277a6
configure packages after install 2026-01-01 17:27:35 -05:00
09f1ae6854
try to update plugins 2026-01-01 17:04:54 -05:00
995aaac2a5
update plugin directory 2026-01-01 14:57:39 -05:00
9b19b61372
switching from handling plugin downloading on our own to use vim.pack 2026-01-01 14:34:49 -05:00
70853bd01e
disabled packages were still getting force loaded 2025-07-10 21:20:53 -04:00
9d4322572c
should've tested the changes, nvim_create_usercommand doesn't like...
unkown options
2025-07-04 05:27:36 -04:00
3b33a604d8
commands get rerun by default now 2025-07-04 05:25:06 -04:00
5aff147731
add better type definitions, and fix a bug related to...
lua indexing my beloved
2025-07-04 05:04:23 -04:00
edf32fbf06
fix bug: remove the callback from the on_load list 2025-07-03 18:02:44 -04:00
adec93b7f4
fix formatting log lines causing errors 2025-07-03 17:02:33 -04:00
1cd5f63f8e
actually error when we fail to setup lazy loading for a package 2025-07-03 17:00:46 -04:00
8e46eddecd
Allow users to lazy load on another package 2025-07-03 16:31:04 -04:00
542298c1fe
registering a filetype lazy load condition should happen on self not the...
lazy_loader
2025-07-02 21:51:34 -04:00
cfc3f08d53
remove a testcase for specs 2025-07-02 21:13:30 -04:00
1c2f49fcfa
unpin dep internally 2025-07-02 21:13:08 -04:00
f6209048f1
Revert "use the correct_spec function to handle dependencies and requirements"
This reverts commit 318a86d786.
2025-07-02 21:07:04 -04:00
5bd30d9397
don't error when the ui is closed 2025-07-02 20:36:14 -04:00
1538046b6f
make finding modules synchronous to avoid some bugs when calling...
internal neovim api functions
2025-07-02 20:34:42 -04:00
5deffca36e
improve lazy loading on commands, dep will now load on completion 2025-07-02 17:54:43 -04:00
318a86d786
use the correct_spec function to handle dependencies and requirements 2025-07-02 15:00:37 -04:00
5541a656e2
add tests for the spec checker 2025-07-02 15:00:00 -04:00
59a963a5c5
update the LICENSE 2025-07-02 04:23:14 -04:00
cd43b3b853
add git pre-commit hooks 2025-07-02 04:15:58 -04:00
585f04c745
add testing...
this is very limited right now, but I will expand as I see fit
2025-07-02 03:53:31 -04:00
a13d616da5
add documentation 2025-07-02 03:11:20 -04:00
26ef7289f6
update readme 2025-07-01 23:34:32 -04:00
d406ee18f2
allow never syncing 2025-07-01 23:34:00 -04:00
e0f39fe0db
allow disabling modules 2025-07-01 23:33:33 -04:00
e8276e3137
ensure that the setup step doesn't get run multiple times when opening...
the ui
2025-07-01 21:57:08 -04:00
44ec0633ce
add a ui (wip) 2025-07-01 21:44:43 -04:00
fa3457e463
formatting 2025-07-01 21:38:41 -04:00
f4d1c4cf25
reorganize the main file a little bit 2025-07-01 21:38:10 -04:00
2346a0baa5
remove lazied field from package as it was unused 2025-07-01 15:55:37 -04:00
8b91fc9c2f
bench doesn't need to be a class 2025-06-29 18:12:47 -04:00
b60e6a3934
allow lazy loading colorschemes 2025-06-29 18:12:30 -04:00
ac14e3d5bb
dep can now search for modules as long as it's provided with a prefix 2025-06-28 15:21:01 -04:00
07df092fc6
add type annotation for the main function 2025-06-27 18:03:16 -04:00
1623276cb0
allow for modules to keep track of the packages they've declared 2025-06-27 18:02:50 -04:00
92cf3634c9
move lazy loading helpers inside of the dep folder 2025-06-27 18:01:46 -04:00
84ee8414f7
add optional parameters to speclist type 2025-06-27 18:00:36 -04:00
84ac4aef17
add better logging to modules 2025-06-27 17:59:15 -04:00
b7218c64c2
make sure the log doesn't get filled with redundant info 2025-06-24 17:20:37 -04:00
0789260637
add a known issues section 2025-06-24 17:20:22 -04:00
561f1d8f8b
add a description field to modules 2025-06-24 17:19:42 -04:00
fd1c5d6e79
make lazy loading better...
- add a ft function to make it better when trying to load a plugin on a
  ft
- add shorthands to make it easier to create one load condition
2025-06-24 03:08:24 -04:00
6bd6db3c02
refine module support and package loading 2025-06-24 02:05:19 -04:00
eba21a8021
do my best to prevent nuking the plugins especially dep 2025-06-23 05:17:07 -04:00
71b78bfca4
the spec no longer fixes itself...
modules are more reliable
cleanup some typos
2025-06-23 00:18:33 -04:00
30cc9a8d50 improve logger 2025-05-06 17:13:58 -05:00
e0814ff507 add more logging to fs 2025-05-06 17:13:36 -05:00
47796d597e add comments to fs 2025-05-06 17:13:28 -05:00
d6c37cf364 refactor: extract spec 2025-05-06 17:12:43 -05:00
3013d714e0 replace pairs with ipairs where necessary 2025-04-28 14:46:05 -05:00
a0bfaefe7c add support for modules again 2025-04-28 14:34:51 -05:00
a3a3652294 move clean() to the fs file 2025-04-26 00:05:16 -05:00
2ccbb7ea74 add stricter checking on urls and paths, and make sure that if one
package fails to load other may continue to load
2025-04-25 23:58:33 -05:00
dfb1820a8e remove todo for loading local plugins 2025-04-25 23:19:37 -05:00
a28fd8f2e6 remove useless string.format(s) 2025-04-25 23:18:49 -05:00
d030a5c39b enable the ability to add local filesystem plugins 2025-04-25 23:18:15 -05:00
381f473a15 fix directory concatination 2025-04-25 23:14:00 -05:00
85a0755af7 add more TODOs to dep.lua 2025-04-25 16:22:35 -05:00
2b9498c5fd remove stylua.toml 2025-04-25 16:22:03 -05:00
a6bf2a2637 remove once property from auto commands (they get deleted anyways) 2025-04-25 16:21:16 -05:00
6dd68240ac update README 2025-04-25 16:20:45 -05:00
125d83ccf9 add some logging to the lazy utils 2025-04-25 16:20:23 -05:00
3b7963ab0a add helper file to deal with aliases 2025-04-25 16:14:28 -05:00
452414cafb update the README with more relevant information 2025-04-24 14:40:13 -05:00
d217ffa0b6 make dep only require nvim 0.8, and make the lazy utils better 2025-04-24 14:38:25 -05:00
296dc11c93 remove unnecessary log, and add a TODO for local plugins 2025-04-23 15:16:15 -05:00
2267d17d25 Update readme and add myself to the licence 2025-04-23 15:12:48 -05:00
c29395004d more fixes 2025-04-23 13:53:47 -05:00
254436c24d it's working now, but some of the logging is very redundant 2025-04-22 17:49:53 -05:00
8bcc8bc0b1 try to clean it up (not working yet) 2025-04-22 17:32:43 -05:00
1d0b486e08 Dep now supports lazy loading 2025-04-21 15:32:41 -05:00
21 changed files with 2215 additions and 1307 deletions

11
.githooks/pre-commit Executable file
View file

@ -0,0 +1,11 @@
#!/bin/sh
echo "Running tests before commit..."
# run tests
make test || {
echo "Tests failed. Commit aborted."
exit 1
}
echo "Tests passed. Proceeding with commit."

1
.gitignore vendored
View file

@ -1 +1,2 @@
.DS_Store .DS_Store
doc/tags

View file

@ -1,6 +1,7 @@
MIT License MIT License
Copyright (c) 2021 chiya.dev Copyright (c) 2023-2026 squibid
Copyright (c) 2021-2023 chiya.dev
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

4
Makefile Normal file
View file

@ -0,0 +1,4 @@
test:
nvim --headless -c "PlenaryBustedDirectory tests/ {minimal_init = './tests/minit.lua'}"
.PHONY: test

185
README.md
View file

@ -1,27 +1,28 @@
# 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.
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]. Originally written for personal use by [luaneko][4]. Adapted by [squibid][5] for
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 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. 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
(barring any lazy loading).
See also luaneko's [neovim-configs][10] 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-config][10] for an example of how dep can be used in
practice.
## Requirements ## Requirements
- [Neovim][2] 0.6+ - [Neovim][2] 0.8+
- [Git][5] - [Git][6] 2.13+
## Setup ## Setup
@ -29,11 +30,11 @@ See also luaneko's [neovim-configs][10] for an example of how dep can be used in
```lua ```lua
-- ~/.config/nvim/lua/bootstrap.lua: -- ~/.config/nvim/lua/bootstrap.lua:
-- automatically install `chiyadev/dep` on startup -- automatically install `squibid/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://github.com/chiyadev/dep", path }) vim.fn.system({ "git", "clone", "--depth=1", "https://git.squi.bid/squibid/dep", path })
end end
vim.cmd("packadd dep") vim.cmd("packadd dep")
@ -54,9 +55,8 @@ 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. - `:DepUi` - opens the ui.
## Package specification ## Package specification
@ -68,42 +68,55 @@ 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|true] Code used to determine when the package should be loaded.
lazy = function(load)
load:cmd("LoadPackage")
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",
-- [string] Overrides the URL of the git repository to clone. -- [string] Overrides the URL of the git repository to clone.
-- 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.squi.bid/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",
-- [string] Overrides the commit ref to target
-- Defaults to the latest commit on the current branch
commit = "e76cb03",
-- [boolean] Prevents the package from being loaded. -- [boolean] Prevents the package from being loaded.
disable = true, disable = true,
-- [boolean] Prevents the package from being updated. -- [boolean] Prevents the package from being updated.
pin = true, 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. -- If given a string, it is wrapped into an array.
requires = {...}, reqs = {...},
-- [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.
@ -129,7 +142,7 @@ combined into one. This is useful when declaring dependencies, which is explored
require "dep" { require "dep" {
{ {
"user/package", "user/package",
requires = "user/dependency", reqs = "user/dependency",
disabled = true, disabled = true,
config = function() config = function()
print "my config hook" print "my config hook"
@ -150,7 +163,7 @@ require "dep" {
require "dep" { require "dep" {
{ {
"user/package", "user/package",
requires = { "user/dependency", "user/another_dependency" }, reqs = { "user/dependency", "user/another_dependency" },
deps = "user/dependent", deps = "user/dependent",
disabled = true, disabled = true,
config = function() config = function()
@ -171,10 +184,10 @@ they are combined into one just like normal package specifications.
require "dep" { require "dep" {
{ {
"user/package", "user/package",
requires = { reqs = {
{ {
"user/dependency1", "user/dependency1",
requires = "user/dependency2" reqs = "user/dependency2"
} }
} }
} }
@ -197,7 +210,7 @@ require "dep" {
require "dep" { require "dep" {
{ {
"user/dependency1", "user/dependency1",
requires = "user/dependency2", reqs = "user/dependency2",
deps = "user/package" deps = "user/package"
} }
} }
@ -206,11 +219,11 @@ require "dep" {
require "dep" { require "dep" {
{ {
"user/dependency1", "user/dependency1",
requires = "user/dependency2" reqs = "user/dependency2"
}, },
{ {
"user/package", "user/package",
requires = "user/dependency1" reqs = "user/dependency1"
} }
} }
@ -237,11 +250,11 @@ instead of hanging or crashing.
require "dep" { require "dep" {
{ {
"user/package1", "user/package1",
requires = "user/package2" reqs = "user/package2"
}, },
{ {
"user/package2", "user/package2",
requires = "user/package1" reqs = "user/package1"
} }
} }
``` ```
@ -257,12 +270,12 @@ require "dep" {
{ {
"user/package1", "user/package1",
disabled = true, -- implied disabled = true, -- implied
requires = "user/dependency" reqs = "user/dependency"
}, },
{ {
"user/package2", "user/package2",
disabled = true, -- implied disabled = true, -- implied
requires = "user/dependency" reqs = "user/dependency"
} }
} }
``` ```
@ -273,31 +286,85 @@ If a dependency fails to load for some reason, all of its dependents are guarant
require "dep" { require "dep" {
{ {
"user/problematic", "user/problematic",
function() load = function()
error("bad hook") error("bad hook")
end end
}, },
{ {
"user/dependent", "user/dependent",
requires = "user/problematic", requires = "user/problematic",
function() load = 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/loader/init.lua` for the source code.
## Separating code into modules ## Separating code into modules
Suppose you split your `init.lua` into two files `packages/search.lua` and Suppose you split your `init.lua` into two files `packages/search.lua` and
`packages/vcs.lua`, which declare the packages [telescope.nvim][6] and [vim-fugitive][7] respectively. `packages/vcs.lua`, which declare the packages [telescope.nvim][7] and [vim-fugitive][8] respectively.
```lua ```lua
-- ~/.config/nvim/lua/packages/search.lua: -- ~/.config/nvim/lua/packages/search.lua:
return { return {
{ {
"nvim-telescope/telescope.nvim", "nvim-telescope/telescope.nvim",
requires = "nvim-lua/plenary.nvim" reqs = "nvim-lua/plenary.nvim"
} }
} }
``` ```
@ -312,6 +379,13 @@ return {
Package specifications from other modules can be loaded using the `modules` option. Package specifications from other modules can be loaded using the `modules` option.
```lua ```lua
require "dep" {
modules = {
prefix = "packages"
}
}
-- the above is equivalent to
require "dep" { require "dep" {
modules = { modules = {
prefix = "packages.", prefix = "packages.",
@ -320,7 +394,7 @@ require "dep" {
} }
} }
-- the above is equivalent to -- or
require "dep" { require "dep" {
modules = { modules = {
"packages.search", "packages.search",
@ -345,7 +419,7 @@ require("dep")(packages)
require "dep" { require "dep" {
{ {
"nvim-telescope/telescope.nvim", "nvim-telescope/telescope.nvim",
requires = "nvim-lua/plenary.nvim" reqs = "nvim-lua/plenary.nvim"
}, },
"tpope/vim-fugitive" "tpope/vim-fugitive"
} }
@ -362,7 +436,7 @@ return {
{ {
"user/package", "user/package",
disabled = true, -- implied by module disabled = true, -- implied by module
requires = { reqs = {
{ {
"user/dependency", "user/dependency",
-- disabled = true -- not implied -- disabled = true -- not implied
@ -403,6 +477,19 @@ require "dep" {
} }
``` ```
## Known Issues
- Lazy loading nvim-cmp doesn't work as the external sources don't get reconized
by nvim-cmp when it's loaded.
## Contributing
When contributing you may choose to run tests before commiting changes, if that
is so you may choose to run the following:
```sh
git config core.hooksPath .githooks
```
## License ## License
dep is licensed under the [MIT License](LICENSE). dep is licensed under the [MIT License](LICENSE).
@ -411,9 +498,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://git-scm.com/ [5]: https://squi.bid
[6]: https://github.com/nvim-telescope/telescope.nvim [6]: https://git-scm.com/
[7]: https://github.com/tpope/vim-fugitive [7]: https://github.com/nvim-telescope/telescope.nvim
[8]: https://GitHub.com/chiyadev/dep/issues [8]: https://github.com/tpope/vim-fugitive
[9]: https://github.com/chiyadev/dep/graphs/contributors [9]: https://github.com/lewis6991/gitsigns.nvim
[10]: https://github.com/luaneko/neovim-config [10]: https://git.squi.bid/nvim

670
doc/dep.txt Normal file
View file

@ -0,0 +1,670 @@
*dep.txt* Declarative Package Manager 02-Jul-2025
==============================================================================
Table of Contents *dep-table-of-contents*
1. Introduction |dep|
2. Setup |dep-setup|
3. Specs |dep-spec|
- Package Spec |dep-package-spec|
- Module Spec |dep-module-spec|
4. Lazy Loading |dep-lazy-loading|
- Lazy Loading API |dep-lazy-loading-api|
- Lazy Loading API Shorthands |dep-lazy-loading-api-shorthands|
5. Commands |dep-commands|
6. Examples |dep-examples|
- Declaring Dependencies |dep-examples-declaring-dependencies|
- Modules |dep-examples-modules|
- Lazy Loading |dep-examples-lazy-loading|
7. Credits & License |dep-credits|
==============================================================================
1. Introduction *dep*
A versatile, declarative and correct neovim package manager in Lua. Originally
written for personal use by luaneko. Adapted by squibid for general use.
What does that mean?
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
(barring any lazy loading).
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).
==============================================================================
2. Setup *dep-setup*
Put the following anywhere before any actual use of dep.
>lua
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://git.squi.bid/squibid/dep",
path,
})
end
vim.cmd("packadd dep")
<
==============================================================================
3. Specs *dep-spec*
dep uses a variation of specifications to ensure everything works smoothly.
This includes a basic spec used when setting up dep:
>lua
require("dep") {
-- [string] Specifies when dep should automatically synchronize.
-- "never": disable this behavior
-- "new": only install newly declared packages (default)
-- "always": synchronize all packages on startup
sync = "new",
-- [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.
--
-- "."'s are added between the prefix and module names as required. In
-- addition if there is only a prefix and no modules supplied then dep
-- automatically loads all lua files in the directory.
modules = {
-- [string] Prefix string to prepend to all module names. This is
-- optional.
prefix = "",
-- [string] module names
...
},
-- [table|string] package specification(s)
...
}
<
PACKAGE SPEC *dep-package-spec*
>lua
{
-- [string] Specifies the full name of the package.
-- This is the only required field; all other fields are optional.
"user/package",
-- [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|true] Code used to determine when the package should be
-- loaded.
lazy = function(load)
load:cmd("LoadPackage")
end,
-- [string] Overrides the short name of the package.
-- Defaults to a substring of the full name after '/'.
as = "custom_package",
-- [string] Overrides the URL of the git repository to clone.
-- Defaults to "https://github.com/{full_name}.git".
url = "https://git.squi.bid/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",
-- [string] Overrides the commit ref to target
-- Defaults to the latest commit on the current branch
commit = "e76cb03",
-- [boolean] Prevents the package from being loaded.
disable = true,
-- [boolean] Prevents the package from being updated.
pin = true,
-- [string|array] Specifies requirements that must be loaded before the
-- package. If given a string, it is wrapped into an array.
reqs = {...},
-- [string|array] Specifies dependents that must be loaded after the
-- package. If given a string, it is wrapped into an array.
deps = {...}
}
<
MODULE SPEC *dep-module-spec*
>lua
{
-- [string] Specifies a name for the module
name = "a name",
-- [string] Specifies a description of the module
desc = "a description of the module",
-- [boolean] Prevents all packages in the module from being loaded.
disable = false,
-- [table|string] package specification(s)
...
}
<
More information on the package specifications may be found in
|dep-package-spec|.
==============================================================================
4. Lazy Loading *dep-lazy-loading*
Lazy loading is important for making sure neovim can load nice and fast unlike
a certain bloated IDE. It has a seperate section in this documentation to
ensure that you can use it to it's full extent.
If you refer to the |dep-package-spec| you'll notice the `lazy` flag which may
be used to conditionally load a package. When it is set to a function you
choose when it runs and more information on that may be found in
|dep-lazy-loading-api|. In addition to setting it to a function you may set it
to `true` in which case dep takes care of loading it for you.
When setting a colorscheme dep checks to make sure that the plugin is loaded,
therefore it's recommended that you make use of the `lazy` flags ability to be
set to `true` by setting any colorscheme that you have installed, but do not
use as your main one to lazy.
LAZY LOADING API *dep-lazy-loading-api*
Within the |dep-package-spec| the lazy flag when set to a function takes one
argument `load` which is a class containing loading functions. For the
following examples assume that `load` is set to the class which may be found
within `lua/dep/lazy/loader/init.lua`.
------------------------------------------------------------------------------
LOAD:CMD *dep-lazy-loading-api-cmd*
`load:cmd` is a function which allows you to specify a command for the package
to load on. It takes the similar arguments to |nvim_create_user_command()|
with a key difference in what the command runs. The following is an example of
what arguments the function takes:
>lua
load:cmd("Command", {})
<
Notice the missing 'command' argument which is found in
|nvim_create_user_command|, this is replaced by a callback function. The above
is equivalent to the following:
>lua
load:cmd("Command", {
callback = function()
load:cleanup()
if (rerun) then
vim.cmd("Command")
end
end
})
<
If you wish the second argument may be completely ommitted. Note the inclusion
of a `rerun` field, this is a parameter which may be passed into the options table
to re-run the binding after loading the package. You may choose to disable the
built-in logic by passing false.
------------------------------------------------------------------------------
LOAD:AUTO *dep-lazy-loading-api-auto*
`load:auto` is a function which allows you to specify an auto command for the
package to load on. It takes the same arguments as |nvim_create_autocmd()|.
The following is an example of using it:
>lua
load:auto("InsertEnter", {})
<
Just like with |nvim_create_autocmd()| you may choose to pass in a 'callback'
by default the above is equivalent to the following:
>lua
load:auto("InsertEnter", {
callback = function()
load:cleanup()
end
})
<
As with `load:cmd` the second argument may be ommitted.
------------------------------------------------------------------------------
LOAD:FT *dep-lazy-loading-api-ft*
`load:ft` is a function which allows you to specify a filetype for the package
to load on. It takes one argument: 'filetype' like so:
>lua
load:ft("lua")
<
Which is equivalent to the following:
>lua
load:auto("FileType", {
pattern = "lua",
callback = function()
load:cleanup()
end
})
<
Note that this is just an expansion of `load:auto` for your convenience.
------------------------------------------------------------------------------
LOAD:KEYMAP *dep-lazy-loading-api-keymap*
`load:keymap` is a function which allows you to specify a keymap for the
package to load on. It takes the similar arguments to |vim.keymap.set()| with a
key difference in what the command runs. The following is an example of what
arguments the function takes:
>lua
load:keymap("n", "<leader>p", {})
<
Notice the missing 'rhs' argument which is found in |vim.keymap.set|, this is
replaced by a callback function. The above is equivalent to the following:
>lua
load:keymap("n", "<leader>p", {
callback = function()
-- register keymap unload
load:cleanup()
-- call the keymap after the user has mapped it
if type(rerun) == "function" then
rerun()
elseif rerun then
local keys = vim.api.nvim_replace_termcodes(bind, true, false, true)
vim.api.nvim_input(keys)
end
end
})
<
Note the inclusion of a `rerun` field, this is a parameter which may be passed
into the options table to re-run the binding after loading the package. You
may choose to include your own logic by passing a function to the `rerun`
field or disable the built-in logic by passing false.
------------------------------------------------------------------------------
LOAD:PLUGIN *dep-lazy-loading-api-plugin*
`load:plugin` is a function which allows you to specify another plugin for the
package to load after. It takes two arguments: `plugin` which is the name of
the plugin you want to follow like: 'user/package'. The second argument is
`opts` which is a table with one option: `callback` which is a function. The
following is an example:
>lua
load:plugin("user/package", {})
<
Which is the same as:
>lua
load:plugin("user/package", {
callback = function()
self:cleanup()
end
})
<
When 'user/package' is already loaded the `callback` is called immediately.
LAZY LOADING API SHORTHANDS *dep-lazy-loading-api-shorthands*
On occasion you may wish to only define one condition for the package to load.
When that is the case you may choose to use the built-in shorthands. By
loading them:
>lua
require("dep.lazy.loader.short")
<
The shorthands are very similar to those found in |dep-lazy-loading-api| with
a key exception: instead of running the functions within the body of a
function set as the lazy field to a package specification this is the lazy
field and may be use like so:
>lua
{ "user/package",
lazy = require("dep.lazy.loader.short").cmd("Command")
}
<
And you may of course put the shorthands in a variable to make this actually
shorter:
>lua
local short = require("dep.lazy.loader.short")
{ "user/package",
lazy = short.cmd("Command")
}
<
==============================================================================
5. Commands *dep-commands*
------------------------------------------------------------------------------
SYNC ALL PLUGINS *:DepSync*
- installs new packages, updates packages to the latest versions,
cleans removed packages and reloads packages as necessary.
------------------------------------------------------------------------------
CLEAN REMOVED PLUGINS *:DepClean*
- cleans removed packages.
------------------------------------------------------------------------------
RELOAD ALL PLUGINS *:DepReload*
- reloads all packages.
------------------------------------------------------------------------------
OPEN THE UI *:DepUi*
- opens the ui.
------------------------------------------------------------------------------
OPEN THE LOG *:DepLog*
- opens the log file.
==============================================================================
6. Examples *dep-examples*
When a string is given where a package specification table is expected, it is
assumed to be the package's full name.
>lua
require("dep") {
-- these two are equivalent
"user/package",
{ "user/package" },
}
<
A package can be declared multiple times. Multiple declarations of the same
package are combined into one. This is useful when declaring dependencies,
which is explored later.
>lua
require("dep") {
{ "user/package",
reqs = "user/dependency",
disabled = true,
config = function()
print("my config hook")
end
},
{ "user/package",
requires = "user/another_dependency",
deps = "user/dependent",
disabled = false,
config = function()
os.execute("make")
end
}
}
-- the above is equivalent to
require("dep") {
{ "user/package",
reqs = { "user/dependency", "user/another_dependency" },
deps = "user/dependent",
disabled = true,
config = function()
print("my config hook")
os.execute("make")
end
}
}
<
DECLARING DEPENDENCIES *dep-examples-declaring-dependencies*
The dependencies and dependents declared in a package specification are
themselves package specifications. If a dependency or dependent is declared
multiple times, they are combined into one just like normal package
specifications.
>lua
require("dep") {
{ "user/package",
reqs = {
{ "user/dependency1",
reqs = "user/dependency2"
}
}
}
}
-- the above is equivalent to
require("dep") {
{ "user/dependency2",
deps = {
{ "user/dependency1",
deps = "user/package"
}
}
}
}
-- which is equivalent to
require("dep") {
{ "user/dependency1",
reqs = "user/dependency2",
deps = "user/package"
}
}
-- which is equivalent to
require("dep") {
{ "user/dependency1",
reqs = "user/dependency2"
},
{ "user/package",
reqs = "user/dependency1"
}
}
-- which is equivalent to
require("dep") {
{ "user/dependency2",
deps = "user/dependency1"
},
{ "user/dependency1",
deps = "user/package"
}
}
-- all of the above are guaranteed to load in the following order:
-- dependency2, dependency1, package
<
If dep detects a circular dependency cycle, it reports the problematic packages
instead of hanging or crashing.
>lua
-- this throws an error saying package1 depends on package2 which depends on
-- package1
require("dep") {
{ "user/package1",
reqs = "user/package2"
},
{ "user/package2",
reqs = "user/package1"
}
}
<
A dependency can be marked as disabled, which disables all dependents
automatically.
>lua
require("dep") {
{ "user/dependency",
disabled = true
},
{ "user/package1",
disabled = true, -- implied
reqs = "user/dependency"
},
{ "user/package2",
disabled = true, -- implied
reqs = "user/dependency"
}
}
<
If a dependency fails to load for some reason, all of its dependents are
guaranteed to not load.
>lua
require("dep") {
{ "user/problematic",
load = function()
error("bad hook")
end
},
{ "user/dependent",
requires = "user/problematic",
load = function()
print "unreachable"
end
}
}
<
MODULES *dep-examples-modules*
Suppose you split your `init.lua` into two files `packages/search.lua` and
`packages/vcs.lua`, which declare the packages telescope.nvim and vim-fugitive
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"
}
}
-- the above is equivalent to
require("dep") {
modules = {
prefix = "packages.",
"search",
"vcs"
}
}
-- or
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
}
}
}
}
<
LAZY LOADING *dep-examples-lazy-loading*
Lazy loading is a very complicated topic, and therefore this part of the
documentation assumes you have experience with regular package managment.
Let's go over loading order, and how the lazy loader determines what needs to
be loaded.
Let's say we have the following spec:
>lua
{ "user/package",
lazy = true,
deps = "user/dependent"
}
<
This is the same as the following:
>lua
{ "user/package",
lazy = true
},
{ "user/dependent",
reqs = "user/package",
lazy = require("dep.lazy.loader.short").plugin("user/package")
}
<
What you're seeing is implicit lazy loading. By default dep will lazy load
dependents who are explicitly defined in the spec. Now if we we're to modify
'user/dependent' like so:
>lua
{ "user/package",
lazy = true
},
{ "user/dependent",
reqs = "user/package",
lazy = function(load)
load:plugin("user/package")
load:cmd("LoadDependent")
end
}
<
If we were to call the command `:LoadDependent` it would first load
'user/package', and then load 'user/dependent'.
==============================================================================
7. Credits & License *dep-credits*
dep is licensed under the MIT License. Check the LICENSE file for more info.
vim:tw=78:ts=8:noet:ft=help:norl:

File diff suppressed because it is too large Load diff

32
lua/dep/bench.lua Normal file
View file

@ -0,0 +1,32 @@
-- 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 = {}
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

36
lua/dep/lazy/init.lua Normal file
View file

@ -0,0 +1,36 @@
local packager = require("dep.package")
---@class lazy
local lazy = {}
-- since this is already a ridiculous "optimization" we should really be caching
-- the results of this for when the user keeps on loading the colorscheme that
-- they've lazy loaded, that way we speed up the lazy loading process
local function colorscheme()
-- if a colorscheme doesn't exist attempt load it prior to it being set
vim.api.nvim_create_autocmd("ColorschemePre", {
pattern = vim.fn.getcompletion("", "color"),
callback = function(e)
for _, p in pairs(packager.get_packages()) do
if not p.loaded then
for _, ext in ipairs({ ".lua", ".vim" }) do
local path = p.dir.."/colors/"..e.match..ext
if vim.uv.fs_stat(path) then
p:ensureadded(true)
-- break out of here, we've loaded the colorscheme
return
end
end
end
end
end
})
end
--- setup all lazy handlers
function lazy.setup()
-- start the colorscheme watcher
colorscheme()
end
return lazy

View file

@ -0,0 +1,174 @@
local logger = require('dep.log')
local packager = require('dep.package')
---@class lazy_loader
---@field load function the function to load the plugin
---@field command_ids string[] the commands that have been registered
---@field auto_ids number[] the auto commands that have been registered
---@field keybind_ids table[] the keybinds that have been registered
---@field plugin_ids table[] the plugins that have been registered
local lazy_loader = {}
--- create a new instance of lazy
---@return lazy
function lazy_loader:new()
local o = {}
setmetatable(o, self)
o.command_ids = {}
o.auto_ids = {}
o.keybind_ids = {}
o.plugin_ids = {}
self.__index = self
return o
end
--- set the loading callback
---@param load function the loader function
function lazy_loader:set_load(load)
self.load = 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_loader:cmd(name, 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
-- load the plugin on completion
if not opts["complete"] then
opts["complete"] = function(_, line, _)
self:cleanup()
-- return all completions for the current input, we need this to ensure
-- that the new completions are loaded from the actual plugin, not our
-- definiton of the command
return vim.fn.getcompletion(line, "cmdline")
end
opts["nargs"] = "*"
end
vim.api.nvim_create_user_command(name, opts['callback'] or function(_)
self:cleanup()
-- attempt to rerun the command
if not rerun then
pcall(vim.cmd, name)
end
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_loader: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_loader:ft(filetype)
self:auto("FileType", {
pattern = filetype
})
end
---@class lazy.Opts: vim.keymap.set.Opts
---@field rerun boolean|function weather to rerun and what to do
--- 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 lazy.Opts? options
function lazy_loader: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 type(rerun) == "function" then
rerun()
elseif 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
--- load a plugin when another plugin loads
---@param plugin string plugin name
---@param opts table? options
function lazy_loader:plugin(plugin, opts)
opts = opts or {}
opts["callback"] = opts["callback"] or function()
self:cleanup()
end
if packager.get_packages()[plugin].loaded then
opts["callback"]()
else
local on_load = packager.get_packages()[plugin].on_load
local on_load_idx = #on_load + 1
on_load[on_load_idx] = opts["callback"]
table.insert(self.plugin_ids, { plugin, on_load_idx })
end
end
--- cleanup all the callbacks, and load the plugin
function lazy_loader:cleanup()
-- cleanup user commands
for _, command_id in ipairs(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 ipairs(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 ipairs(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
-- cleanup plugins
for _, plugin_id in ipairs(self.plugin_ids) do
table.remove(packager.get_packages()[plugin_id[1]].on_load, plugin_id[2])
end
-- load the plugin
self:load()
end
return lazy_loader

View file

@ -0,0 +1,72 @@
-- This file contains shorthands which rely on the loader core functions. They
-- are intended to ease lazy loading condition definitions to use them you may
-- do the following:
--
-- ```lua
-- _G.dep_short = require("dep.lazy.loader.short")
-- ```
--
-- Which will allow you to reference it anywhere in your config like so:
--
-- ```lua
-- require("dep") {
-- { "some/plugin",
-- lazy = dep_short.cmd("TheCommand")
-- }
-- }
-- ```
--
-- Happy vimming o/
local short = {}
--- create a single command
---@param name string the name of the command
---@param opts vim.api.keyset.user_command? options
---@return function callback
function short.cmd(name, opts)
return function(load)
load:cmd(name, opts)
end
end
--- create a single auto command
---@param event string the event to trigger on
---@param opts vim.api.keyset.create_autocmd? options
---@return function callback
function short.auto(event, opts)
return function(load)
load:auto(event, opts)
end
end
--- create a single auto command which will trigger on filetype
---@param filetype string filetype to register the auto on
---@return function callback
function short.ft(filetype)
return function(load)
load:ft(filetype)
end
end
--- create a single keybind
---@param mode string the mode to trigger in
---@param bind string the binding to use
---@param opts lazy.Opts? options
---@return function callback
function short.keymap(mode, bind, opts)
return function(load)
load:keymap(mode, bind, opts)
end
end
--- create a single plugin load event for when another plugin loads
---@param plugin string plugin name
---@param opts table? options
---@return function callback
function short.plugin(plugin, opts)
return function(load)
load:plugin(plugin, opts)
end
end
return short

View file

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

74
lua/dep/modules/init.lua Normal file
View file

@ -0,0 +1,74 @@
local logger = require('dep.log')
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, config_path)
overrides = overrides or {}
local o = {}
self = {}
self.__index = self
setmetatable(o, self)
-- create a list of modules
o.modules = {}
if (speclist.modules[1] == "*" or #speclist.modules == 0)
and speclist.modules.prefix then
local path = vim.fs.joinpath(config_path:gsub("[^/]*$", ""),
"lua", (speclist.modules.prefix:gsub("%.", "/"))
)
local handle = vim.uv.fs_scandir(path)
while handle do
local name = vim.uv.fs_scandir_next(handle)
if name then
-- skip non-lua files
if name:sub(#name - 3) ~= ".lua" then
goto continue
end
-- remove the file extension from the name so that lua doesn't fail
-- when attempting to load it
name = name:sub(0, #name - 4)
-- put the module into the list of modules
table.insert(speclist.modules, name)
::continue::
elseif name == nil then
-- no more entries
break
else
-- if there's a single error bail out
logger:log("error", "failed to run clean uv.fs_scandir_next failed")
break
end
end
end
-- loop through all modules and initialize them
for _, modpath in ipairs(speclist.modules) do
local mod = module.new(nil, modpath, speclist.modules.prefix, overrides)
if not mod then
goto continue
end
table.insert(o.modules, mod)
::continue::
end
return self
end
return modules

View file

@ -0,0 +1,73 @@
local logger = require('dep.log')
local spec_man = require("dep.spec")
local packager = require("dep.package")
---@class module
---@field name string name of the module
---@field desc string description of the module
---@field disable boolean weather to disable all the packages inside the module
---@field path string path to the module
---@field mod table the module
---@field packages package[] all packages registed from 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)
overrides = overrides or {}
local ok, err
local o = {}
self = {}
self.__index = self
setmetatable(o, self)
o.name = "<unnamed module>"
o.desc = "<undescribed 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
logger:log("error", "failed to load module: %s", vim.inspect(o.mod))
return false
end
end
o.name = o.mod.name or o.name
o.desc = o.mod.desc or o.desc
-- ensure the overrides are properly set
overrides = vim.tbl_extend("force", overrides, {
disable = o.mod.disable or overrides.disable
})
-- 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
logger:log("error", "%s <- %s", err, o.name)
return false
end
-- ensure that the module contains the packages that it's created
self.packages = err
return self
end
return module

View file

@ -1,216 +1,411 @@
-- local logger = require('dep.log')
-- Copyright (c) 2022 chiya.dev local spec_man = require("dep.spec")
-- 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
local function parse_name_from_id(id) ---@class package
local name = id:match("^[%w-_.]+/([%w-_.]+)$") ---@field id string id of the package
if name then ---@field enabled boolean whether it's going to be used
return name ---@field exists boolean if the package exists on the filesystem
else ---@field lazy boolean if the package is lazy loaded in any way
error(string.format('invalid package name "%s"; must be in the format "user/package"', id)) ---@field added boolean if the package has been added in vim
---@field configured boolean if the package has been configured
---@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 table table of functions and booleans 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
end end
local function is_nonempty_str(s) --- create a new package instance
return type(s) == "string" and #s ~= 0 ---@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
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.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 "file://"..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
-- tell the dep that it's gonna be lazy
pkg.lazy = true
table.insert(pkg.lazy_load,
require("dep.lazy.loader.short").plugin(id))
end
end
end
return o
end end
--- Package information. --- set the base directory for packages
local Package = setmetatable({ ---@param _base_dir string base directory
__metatable = "Package", function package.set_base_dir(_base_dir)
__index = { base_dir = vim.fs.normalize(_base_dir)
--- Runs all registered hooks of the given type. end
run_hooks = function(self, hook)
local hooks = self["on_" .. hook] --- get the base directory for packages
if not hooks or #hooks == 0 then ---@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 return true
end end
local start = os.clock() 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 for i = 1, #hooks do
local ok, err = xpcall(hooks[i], debug.traceback) local ok, err = pcall(hooks[i])
if not ok then if not ok then
vim.fn.chdir(last_cwd)
return false, err return false, err
end end
end end
local elapsed = os.clock() - start vim.fn.chdir(last_cwd)
self.perf.hooks[hook] = elapsed self.perf[type] = os.clock() - start
logger:log( logger:log("hook", "triggered %d %s %s for %s", #hooks, type,
"hook", #hooks == 1 and "hook" or "hooks", self.id)
"triggered %d %s %s for %s in %dms",
#hooks,
hook,
#hooks == 1 and "hook" or "hooks",
self.id,
elapsed
)
return true return true
end, 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. --- make sure a package has been loaded
local PackageStore = setmetatable({ ---@param force boolean? force lazy packages to load
__metatable = "PackageStore", ---@return boolean|table return true or false if loaded or package spec if lazy loaded
__index = { function package:ensureadded(force)
--- Links the given packages such that the parent must load before the child. --- load a package
link_dependency = function(self, parent, child) ---@param pkg package
if not parent.dependents[child.id] then local function loadpkg(pkg)
parent.dependents[child.id] = child if not self.enabled then
parent.dependents[#parent.dependents + 1] = child return false
end end
if not child.dependencies[parent.id] then -- make sure to load the dependencies first
child.dependencies[parent.id] = parent for _, p in pairs(pkg.requirements) do
child.dependencies[#child.dependencies + 1] = parent if not p.loaded then
end p:ensureadded(true)
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])
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")
assert(specs.modules == nil or type(specs.modules) == "table", "package list module list must be a table")
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
-- recursively add referenced spec list modules
if specs.modules then
local prefix = specs.modules.prefix or ""
for i = 1, #specs.modules do
local name = specs.modules[i]
assert(type(name) == "string", "package list inner module name must be a string")
name = prefix .. name
local module = require(name)
assert(type(module) == "table", "package list inner module did not return a spec list table")
self:add_specs(module, scope)
end end
end end
end,
--- Ensures there are no circular dependencies in this package store. -- run setup hooks
ensure_acyclic = function(self) pkg:runhooks("on_setup")
-- tarjan's strongly connected components algorithm
local idx, indices, lowlink, stack = 0, {}, {}, {}
-- 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)
for _, load_cond in pairs(self.lazy_load) do
-- configure the lazy loader for the user
local l = require('dep.lazy.loader'):new()
if l == true then
logger:log("lazy", "failed to get lazy utils")
return false
end
l:set_load(function()
-- ensure that we can't attempt to load a plugin twice via lazy loading
if self.loaded then
return
end
logger:log("lazy", "triggered %d lazy hooks for %s", #self.lazy_load,
self.id)
loadpkg(self)
end)
-- run it if it's not just a stopper to keep the plugin lazy
if load_cond ~= true then
local ok, err = pcall(load_cond, l)
if not ok then
logger:log("error", "failed to register lazy load conditions for '%s': %s",
self.name, err)
end
end
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) local function connect(pkg)
indices[pkg.id], lowlink[pkg.id] = idx, idx indexes[pkg.id], lowlink[pkg.id] = index, index
stack[#stack + 1], stack[pkg.id] = pkg, true stack[#stack + 1], stack[pkg.id] = pkg, true
idx = idx + 1 index = index + 1
for i = 1, #pkg.dependents do for i = 1, #pkg.dependents do
local dependent = pkg.dependents[i] local dependent = pkg.dependents[i]
if not indices[dependent.id] then if not indexes[dependent.id] then
local cycle = connect(dependent) local cycle = connect(dependent)
if cycle then if cycle then
return cycle return cycle
@ -218,11 +413,11 @@ local PackageStore = setmetatable({
lowlink[pkg.id] = math.min(lowlink[pkg.id], lowlink[dependent.id]) lowlink[pkg.id] = math.min(lowlink[pkg.id], lowlink[dependent.id])
end end
elseif stack[dependent.id] then elseif stack[dependent.id] then
lowlink[pkg.id] = math.min(lowlink[pkg.id], indices[dependent.id]) lowlink[pkg.id] = math.min(lowlink[pkg.id], indexes[dependent.id])
end end
end end
if lowlink[pkg.id] == indices[pkg.id] then if lowlink[pkg.id] == indexes[pkg.id] then
local cycle = { pkg } local cycle = { pkg }
local node local node
@ -232,42 +427,85 @@ local PackageStore = setmetatable({
cycle[#cycle + 1] = node cycle[#cycle + 1] = node
until node == pkg until node == pkg
-- a node is by definition strongly connected to itself -- a node is by definition strongly connected to itself ignore single-node
-- ignore single-node components unless the package explicitly specified itself as a dependency (i.e. the user is being weird) -- components unless it explicitly specified itself as a dependency
if #cycle > 2 or pkg.dependents[pkg.id] then if #cycle > 2 or pkg.dependents[pkg.id] then
return cycle return cycle
end end
end end
end end
for i = 1, #self do -- actually check the cycle
local pkg = self[i] for _, pkg in pairs(pkgs) do
if not indexes[package.id] then
if not indices[pkg.id] then
local cycle = connect(pkg) local cycle = connect(pkg)
if cycle then if cycle then
-- found dependency cycle return 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
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 { return false
Package = Package, end
PackageStore = PackageStore,
} --- recurse over all packages and register them
---@param speclist speclist table of specs
---@param overrides spec? a package spec that is used to override options
---@return package[] packages
function package.register_speclist(speclist, overrides)
overrides = overrides or {}
local packages_from_speclist = {}
-- recurse the packages
local over = overrides
for _, spec in ipairs(speclist) do
-- make sure the overrides override and take into account the packages spec
---@diagnostic disable-next-line: missing-fields
over = {
pin = overrides.pin or spec.pin,
disable = overrides.disable or spec.disable
}
-- While a package can fail to load we just don't care, it will work itself
-- out. The goal is to make sure every plugin that can load does load, not
-- keep working plugins from loading because an unrelated one doesn't load.
local pkg = package:new(spec, over)
if not pkg then
goto continue
end
-- we store all the packages in a table so that the caller may keep track of
-- their packages, this is not required and therefore the return value may
-- be discarded
table.insert(packages_from_speclist, pkg)
::continue::
end
return packages_from_speclist
end
--- reload the package
---@param self package the package to reload
---@param force boolean? force all packages to load
function package:reload(force)
local reloaded = self:loadtree(force)
if reloaded then
local ok, err
-- TODO: make a benchmark function
bench.mark("reload", function()
ok, err = pcall(vim.cmd,
[[
silent! helptags ALL
silent! UpdateRemotePlugins
]])
end)
if not ok then
logger:log("error",
"failed to reload helptags and remote plugins; reason: %s", err)
end
end
end
return package

View file

@ -1,64 +0,0 @@
local logger = require("dep.log").global
local proc = {}
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 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)
vim.fn.jobstart(args, {
cwd = cwd,
env = env,
stdin = nil,
on_exit = cb_exit,
on_stdout = cb_output,
on_stderr = cb_output,
})
end
local git_env = { GIT_TERMINAL_PROMPT = 0 }
function proc.git_rev_parse(dir, arg, cb)
local args = { "rev-parse", "--short", arg }
proc.exec("git", args, dir, git_env, cb)
end
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
end
proc.exec("git", args, nil, git_env, cb)
end
function proc.git_fetch(dir, remote, refspec, cb)
local args = { "fetch", "--depth=1", "--recurse-submodules", remote, refspec }
proc.exec("git", args, dir, git_env, cb)
end
function proc.git_reset(dir, treeish, cb)
local args = { "reset", "--hard", "--recurse-submodules", treeish, "--" }
proc.exec("git", args, dir, git_env, cb)
end
return proc

212
lua/dep/spec.lua Normal file
View file

@ -0,0 +1,212 @@
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 base_dir string? the base directory for all plugins
---@field sync ("new"|"always")? when to sync (defaults to new)
---@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|true? 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" and self.lazy ~= true then
logger:log("spec", "spec.lazy must be a function or boolean 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

View file

@ -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

View file

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

36
tests/dep_spec.lua Normal file
View file

@ -0,0 +1,36 @@
---@diagnostic disable: undefined-global, undefined-field
local dep_spec_man = require("dep.spec")
describe("package specification", function()
it("gets the package's name", function()
assert.equal(dep_spec_man.get_name({ "user/package" }), "package")
assert.equal(dep_spec_man.get_name({ "user/package.git" }), "package.git")
end)
it("ensurses specs are in the proper format", function()
local correct = { "user/package" }
assert.same(dep_spec_man.correct_spec("user/package"), correct)
assert.same(dep_spec_man.correct_spec({ "user/package" }), correct)
assert.same(dep_spec_man.correct_spec({ { "user/package" } }), correct)
end)
it("checks a spec for correctness", function()
assert.same(
dep_spec_man.check({ "user/package" }, true),
{ "user/package" }
)
assert.same(
dep_spec_man.check({
"user/package",
deps = "user/dependency"
}, true),
{
"user/package",
deps = {
"user/dependency"
}
}
)
end)
end)

1
tests/minit.lua Normal file
View file

@ -0,0 +1 @@
vim.opt.rtp:prepend(".")