Skip to content

Commit 8eba74c

Browse files
committed
feat: packspec
1 parent 4ea9fe0 commit 8eba74c

File tree

6 files changed

+190
-2
lines changed

6 files changed

+190
-2
lines changed

lua/lazy/core/config.lua

+13
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ M.defaults = {
179179
-- Track each new require in the Lazy profiling tab
180180
require = false,
181181
},
182+
packspec = {
183+
enabled = true,
184+
versions = true, -- Honor dependency versions in packspecs
185+
path = vim.fn.stdpath("state") .. "/lazy/packspec.lua",
186+
},
182187
debug = false,
183188
}
184189

@@ -281,6 +286,14 @@ function M.setup(opts)
281286
require("lazy.manage.checker").start()
282287
end, 10)
283288
end
289+
290+
-- useful for plugin developers when making changes to a packspec file
291+
vim.api.nvim_create_autocmd("BufWritePost", {
292+
pattern = "package.lua",
293+
callback = function()
294+
require("lazy.view.commands").cmd("packspec")
295+
end,
296+
})
284297
end,
285298
})
286299

lua/lazy/core/packspec.lua

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
local Config = require("lazy.core.config")
2+
local Util = require("lazy.util")
3+
4+
---@class PackSpec
5+
---@field dependencies? table<string, string>
6+
---@field lazy? LazyPluginSpec
7+
local M = {}
8+
9+
M.lazy_file = "lazy.lua"
10+
M.pkg_file = "pkg.json"
11+
M.enable_lazy_file = false
12+
13+
---@alias LazyPkg {lazy?:(fun():LazySpec), pkg?:PackSpec}
14+
15+
---@type table<string, LazyPkg>
16+
M.packspecs = nil
17+
---@type table<string, LazySpec>
18+
M.specs = {}
19+
20+
---@param spec LazyPkg
21+
---@param plugin LazyPlugin
22+
---@return LazySpec?
23+
local function convert(plugin, spec)
24+
---@type LazySpec
25+
local ret = {}
26+
27+
local pkg = spec.pkg
28+
if pkg then
29+
if pkg.dependencies then
30+
for url, version in pairs(pkg.dependencies) do
31+
if (not Config.options.packspec.versions) or version == "*" or version == "" then
32+
version = nil
33+
end
34+
-- HACK: Add `.git` to github urls
35+
if url:find("github") and not url:match("%.git$") then
36+
url = url .. ".git"
37+
end
38+
ret[#ret + 1] = { url = url, version = version }
39+
end
40+
end
41+
local p = pkg.lazy
42+
if p then
43+
p.url = p.url or plugin.url
44+
p.dir = p.dir or plugin.dir
45+
ret[#ret + 1] = p
46+
end
47+
end
48+
49+
if spec.lazy then
50+
ret[#ret + 1] = spec.lazy()
51+
end
52+
53+
return ret
54+
end
55+
56+
local function load()
57+
Util.track("packspec")
58+
M.packspecs = {}
59+
if vim.loop.fs_stat(Config.options.packspec.path) then
60+
Util.try(function()
61+
M.packspecs = loadfile(Config.options.packspec.path)()
62+
end, "Error loading packspecs:")
63+
end
64+
Util.track()
65+
end
66+
67+
---@param plugin LazyPlugin
68+
---@return LazySpec?
69+
function M.get(plugin)
70+
if not M.packspecs then
71+
load()
72+
end
73+
74+
if not M.packspecs[plugin.dir] then
75+
return
76+
end
77+
M.specs[plugin.dir] = M.specs[plugin.dir] or convert(plugin, M.packspecs[plugin.dir])
78+
return vim.deepcopy(M.specs[plugin.dir])
79+
end
80+
81+
function M.update()
82+
local ret = {}
83+
for _, plugin in pairs(Config.plugins) do
84+
local spec = {
85+
pkg = M.pkg(plugin),
86+
lazy = M.enable_lazy_file and M.lazy_pkg(plugin) or nil,
87+
}
88+
if not vim.tbl_isempty(spec) then
89+
ret[plugin.dir] = spec
90+
end
91+
end
92+
local code = "return " .. Util.dump(ret)
93+
Util.write_file(Config.options.packspec.path, code)
94+
M.packspecs = nil
95+
M.specs = {}
96+
end
97+
98+
---@param plugin LazyPlugin
99+
function M.lazy_pkg(plugin)
100+
local file = Util.norm(plugin.dir .. "/" .. M.lazy_file)
101+
if Util.file_exists(file) then
102+
---@type LazySpec
103+
local chunk = Util.try(function()
104+
return loadfile(file)
105+
end, "`" .. M.lazy_file .. "` for **" .. plugin.name .. "** has errors:")
106+
if chunk then
107+
return { _raw = ([[function() %s end]]):format(Util.read_file(file)) }
108+
else
109+
Util.error("Invalid `package.lua` for **" .. plugin.name .. "**")
110+
end
111+
end
112+
end
113+
114+
---@param plugin LazyPlugin
115+
function M.pkg(plugin)
116+
local file = Util.norm(plugin.dir .. "/" .. M.pkg_file)
117+
if Util.file_exists(file) then
118+
---@type PackSpec
119+
return Util.try(function()
120+
return vim.json.decode(Util.read_file(file))
121+
end, "`" .. M.pkg_file .. "` for **" .. plugin.name .. "** has errors:")
122+
end
123+
end
124+
125+
return M

lua/lazy/core/plugin.lua

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local Config = require("lazy.core.config")
2+
local Packspec = require("lazy.core.packspec")
23
local Util = require("lazy.core.util")
34

45
---@class LazyCorePlugin
@@ -15,6 +16,8 @@ M.loading = false
1516
---@field notifs {msg:string, level:number, file?:string}[]
1617
---@field importing? string
1718
---@field optional? boolean
19+
---@field packspecs table<string, boolean>
20+
---@field names table<string,string>
1821
local Spec = {}
1922
M.Spec = Spec
2023
M.last_fid = 0
@@ -32,7 +35,9 @@ function Spec.new(spec, opts)
3235
self.dirty = {}
3336
self.notifs = {}
3437
self.ignore_installed = {}
38+
self.packspecs = {}
3539
self.optional = opts and opts.optional
40+
self.names = {}
3641
if spec then
3742
self:parse(spec)
3843
end
@@ -45,6 +50,7 @@ function Spec:parse(spec)
4550
end
4651

4752
-- PERF: optimized code to get package name without using lua patterns
53+
---@return string
4854
function Spec.get_name(pkg)
4955
local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or pkg
5056
name = name:sub(-1) == "/" and name:sub(1, -2) or name
@@ -83,7 +89,17 @@ function Spec:add(plugin, results)
8389
-- local plugin
8490
plugin.name = plugin.name or Spec.get_name(plugin.dir)
8591
elseif plugin.url then
86-
plugin.name = plugin.name or Spec.get_name(plugin.url)
92+
if plugin.name then
93+
self.names[plugin.url] = plugin.name
94+
local name = Spec.get_name(plugin.url)
95+
if name and self.plugins[name] then
96+
self.plugins[name].name = plugin.name
97+
self.plugins[plugin.name] = self.plugins[name]
98+
self.plugins[name] = nil
99+
end
100+
else
101+
plugin.name = self.names[plugin.url] or Spec.get_name(plugin.url)
102+
end
87103
-- check for dev plugins
88104
if plugin.dev == nil then
89105
for _, pattern in ipairs(Config.options.dev.patterns) do
@@ -156,6 +172,15 @@ function Spec:add(plugin, results)
156172
table.remove(M.fid_stack)
157173
end
158174

175+
-- import the plugin's spec
176+
if Config.options.packspec.enabled and plugin.dir and not self.packspecs[plugin.dir] then
177+
self.packspecs[plugin.dir] = true
178+
local packspec = Packspec.get(plugin)
179+
if packspec then
180+
self:normalize(packspec, nil, true)
181+
end
182+
end
183+
159184
if self.plugins[plugin.name] then
160185
plugin = self:merge(self.plugins[plugin.name], plugin)
161186
end

lua/lazy/manage/init.lua

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ function M.install(opts)
9292
}, opts):wait(function()
9393
require("lazy.manage.lock").update()
9494
require("lazy.help").update()
95+
require("lazy.core.packspec").update()
9596
end)
9697
end
9798

@@ -116,6 +117,7 @@ function M.update(opts)
116117
}, opts):wait(function()
117118
require("lazy.manage.lock").update()
118119
require("lazy.help").update()
120+
require("lazy.core.packspec").update()
119121
end)
120122
end
121123
--

lua/lazy/view/commands.lua

+15-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ M.commands = {
3434
health = function()
3535
vim.cmd.checkhealth("lazy")
3636
end,
37+
pkg = function(opts)
38+
local Pkg = require("lazy.core.packspec")
39+
Pkg.update()
40+
require("lazy.manage.reloader").reload({
41+
{
42+
file = "packspec", --Config.options.packspec.path,
43+
what = "changed",
44+
},
45+
})
46+
for _, plugin in pairs(opts and opts.plugins or {}) do
47+
local spec = Pkg.get(plugin)
48+
Util.info(vim.inspect(spec), { lang = "lua", title = plugin.name })
49+
end
50+
end,
3751
home = function()
3852
View.show("home")
3953
end,
@@ -74,7 +88,7 @@ M.commands = {
7488
}
7589

7690
function M.complete(cmd, prefix)
77-
if not (ViewConfig.commands[cmd] or {}).plugins then
91+
if not (ViewConfig.commands[cmd] or {}).plugins and cmd ~= "pkg" then
7892
return
7993
end
8094
---@type string[]

tests/core/plugin_spec.lua

+9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ describe("plugin spec url/name", function()
3030
{ { "foo/bar", name = "foobar" }, { [1] = "foo/bar", name = "foobar", url = "https://github.com/foo/bar.git" } },
3131
{ { "foo/bar", url = "123" }, { [1] = "foo/bar", name = "123", url = "123" } },
3232
{ { url = "https://foobar" }, { name = "foobar", url = "https://foobar" } },
33+
{
34+
{ { url = "https://foo", name = "foobar" }, { url = "https://foo" } },
35+
{ name = "foobar", url = "https://foo" },
36+
},
37+
{
38+
{ { url = "https://foo" }, { url = "https://foo", name = "foobar" } },
39+
{ name = "foobar", url = "https://foo" },
40+
},
3341
{ { url = "ssh://foobar" }, { name = "foobar", url = "ssh://foobar" } },
3442
{ "foo/bar", { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
3543
{ { { { "foo/bar" } } }, { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
@@ -46,6 +54,7 @@ describe("plugin spec url/name", function()
4654
plugins[1]._ = {}
4755
assert(#spec.notifs == 0)
4856
assert.equal(1, #plugins)
57+
plugins[1]._.super = nil
4958
assert.same(test[2], plugins[1])
5059
end)
5160
end

0 commit comments

Comments
 (0)