Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial packspec support #910

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lua/lazy/core/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ M.defaults = {
-- Track each new require in the Lazy profiling tab
require = false,
},
packspec = {
enabled = true,
versions = true, -- Honor dependency versions in packspecs
path = vim.fn.stdpath("state") .. "/lazy/packspec.lua",
},
debug = false,
}

Expand Down Expand Up @@ -281,6 +286,14 @@ function M.setup(opts)
require("lazy.manage.checker").start()
end, 10)
end

-- useful for plugin developers when making changes to a packspec file
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = "package.lua",
callback = function()
require("lazy.view.commands").cmd("packspec")
end,
})
end,
})

Expand Down
125 changes: 125 additions & 0 deletions lua/lazy/core/packspec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
local Config = require("lazy.core.config")
local Util = require("lazy.util")

---@class PackSpec
---@field dependencies? table<string, string>
---@field lazy? LazyPluginSpec
local M = {}

M.lazy_file = "lazy.lua"
M.pkg_file = "pkg.json"
M.enable_lazy_file = false

---@alias LazyPkg {lazy?:(fun():LazySpec), pkg?:PackSpec}

---@type table<string, LazyPkg>
M.packspecs = nil
---@type table<string, LazySpec>
M.specs = {}

---@param spec LazyPkg
---@param plugin LazyPlugin
---@return LazySpec?
local function convert(plugin, spec)
---@type LazySpec
local ret = {}

local pkg = spec.pkg
if pkg then
if pkg.dependencies then
for url, version in pairs(pkg.dependencies) do
if (not Config.options.packspec.versions) or version == "*" or version == "" then
version = nil
end
-- HACK: Add `.git` to github urls
if url:find("github") and not url:match("%.git$") then
url = url .. ".git"
end
ret[#ret + 1] = { url = url, version = version }
end
end
local p = pkg.lazy
if p then
p.url = p.url or plugin.url
p.dir = p.dir or plugin.dir
ret[#ret + 1] = p
end
end

if spec.lazy then
ret[#ret + 1] = spec.lazy()
end

return ret
end

local function load()
Util.track("packspec")
M.packspecs = {}
if vim.loop.fs_stat(Config.options.packspec.path) then
Util.try(function()
M.packspecs = loadfile(Config.options.packspec.path)()
end, "Error loading packspecs:")
end
Util.track()
end

---@param plugin LazyPlugin
---@return LazySpec?
function M.get(plugin)
if not M.packspecs then
load()
end

if not M.packspecs[plugin.dir] then
return
end
M.specs[plugin.dir] = M.specs[plugin.dir] or convert(plugin, M.packspecs[plugin.dir])
return vim.deepcopy(M.specs[plugin.dir])
end

function M.update()
local ret = {}
for _, plugin in pairs(Config.plugins) do
local spec = {
pkg = M.pkg(plugin),
lazy = M.enable_lazy_file and M.lazy_pkg(plugin) or nil,
}
if not vim.tbl_isempty(spec) then
ret[plugin.dir] = spec
end
end
local code = "return " .. Util.dump(ret)
Util.write_file(Config.options.packspec.path, code)
M.packspecs = nil
M.specs = {}
end

---@param plugin LazyPlugin
function M.lazy_pkg(plugin)
local file = Util.norm(plugin.dir .. "/" .. M.lazy_file)
if Util.file_exists(file) then
---@type LazySpec
local chunk = Util.try(function()
return loadfile(file)
end, "`" .. M.lazy_file .. "` for **" .. plugin.name .. "** has errors:")
if chunk then
return { _raw = ([[function() %s end]]):format(Util.read_file(file)) }
else
Util.error("Invalid `package.lua` for **" .. plugin.name .. "**")
end
end
end

---@param plugin LazyPlugin
function M.pkg(plugin)
local file = Util.norm(plugin.dir .. "/" .. M.pkg_file)
if Util.file_exists(file) then
---@type PackSpec
return Util.try(function()
return vim.json.decode(Util.read_file(file))
end, "`" .. M.pkg_file .. "` for **" .. plugin.name .. "** has errors:")
end
end

return M
27 changes: 26 additions & 1 deletion lua/lazy/core/plugin.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local Config = require("lazy.core.config")
local Packspec = require("lazy.core.packspec")
local Util = require("lazy.core.util")

---@class LazyCorePlugin
Expand All @@ -15,6 +16,8 @@ M.loading = false
---@field notifs {msg:string, level:number, file?:string}[]
---@field importing? string
---@field optional? boolean
---@field packspecs table<string, boolean>
---@field names table<string,string>
local Spec = {}
M.Spec = Spec
M.last_fid = 0
Expand All @@ -32,7 +35,9 @@ function Spec.new(spec, opts)
self.dirty = {}
self.notifs = {}
self.ignore_installed = {}
self.packspecs = {}
self.optional = opts and opts.optional
self.names = {}
if spec then
self:parse(spec)
end
Expand All @@ -45,6 +50,7 @@ function Spec:parse(spec)
end

-- PERF: optimized code to get package name without using lua patterns
---@return string
function Spec.get_name(pkg)
local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or pkg
name = name:sub(-1) == "/" and name:sub(1, -2) or name
Expand Down Expand Up @@ -83,7 +89,17 @@ function Spec:add(plugin, results)
-- local plugin
plugin.name = plugin.name or Spec.get_name(plugin.dir)
elseif plugin.url then
plugin.name = plugin.name or Spec.get_name(plugin.url)
if plugin.name then
self.names[plugin.url] = plugin.name
local name = Spec.get_name(plugin.url)
if name and self.plugins[name] then
self.plugins[name].name = plugin.name
self.plugins[plugin.name] = self.plugins[name]
self.plugins[name] = nil
end
else
plugin.name = self.names[plugin.url] or Spec.get_name(plugin.url)
end
-- check for dev plugins
if plugin.dev == nil then
for _, pattern in ipairs(Config.options.dev.patterns) do
Expand Down Expand Up @@ -156,6 +172,15 @@ function Spec:add(plugin, results)
table.remove(M.fid_stack)
end

-- import the plugin's spec
if Config.options.packspec.enabled and plugin.dir and not self.packspecs[plugin.dir] then
self.packspecs[plugin.dir] = true
local packspec = Packspec.get(plugin)
if packspec then
self:normalize(packspec, nil, true)
end
end

if self.plugins[plugin.name] then
plugin = self:merge(self.plugins[plugin.name], plugin)
end
Expand Down
2 changes: 2 additions & 0 deletions lua/lazy/manage/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ function M.install(opts)
}, opts):wait(function()
require("lazy.manage.lock").update()
require("lazy.help").update()
require("lazy.core.packspec").update()
end)
end

Expand All @@ -116,6 +117,7 @@ function M.update(opts)
}, opts):wait(function()
require("lazy.manage.lock").update()
require("lazy.help").update()
require("lazy.core.packspec").update()
end)
end
--
Expand Down
16 changes: 15 additions & 1 deletion lua/lazy/view/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ M.commands = {
health = function()
vim.cmd.checkhealth("lazy")
end,
pkg = function(opts)
local Pkg = require("lazy.core.packspec")
Pkg.update()
require("lazy.manage.reloader").reload({
{
file = "packspec", --Config.options.packspec.path,
what = "changed",
},
})
for _, plugin in pairs(opts and opts.plugins or {}) do
local spec = Pkg.get(plugin)
Util.info(vim.inspect(spec), { lang = "lua", title = plugin.name })
end
end,
home = function()
View.show("home")
end,
Expand Down Expand Up @@ -74,7 +88,7 @@ M.commands = {
}

function M.complete(cmd, prefix)
if not (ViewConfig.commands[cmd] or {}).plugins then
if not (ViewConfig.commands[cmd] or {}).plugins and cmd ~= "pkg" then
return
end
---@type string[]
Expand Down
9 changes: 9 additions & 0 deletions tests/core/plugin_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ describe("plugin spec url/name", function()
{ { "foo/bar", name = "foobar" }, { [1] = "foo/bar", name = "foobar", url = "https://github.com/foo/bar.git" } },
{ { "foo/bar", url = "123" }, { [1] = "foo/bar", name = "123", url = "123" } },
{ { url = "https://foobar" }, { name = "foobar", url = "https://foobar" } },
{
{ { url = "https://foo", name = "foobar" }, { url = "https://foo" } },
{ name = "foobar", url = "https://foo" },
},
{
{ { url = "https://foo" }, { url = "https://foo", name = "foobar" } },
{ name = "foobar", url = "https://foo" },
},
{ { url = "ssh://foobar" }, { name = "foobar", url = "ssh://foobar" } },
{ "foo/bar", { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
{ { { { "foo/bar" } } }, { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
Expand All @@ -46,6 +54,7 @@ describe("plugin spec url/name", function()
plugins[1]._ = {}
assert(#spec.notifs == 0)
assert.equal(1, #plugins)
plugins[1]._.super = nil
assert.same(test[2], plugins[1])
end)
end
Expand Down
Loading