neovim / packspec

ALPHA package dependencies spec
http://packspec.org/
Apache License 2.0
219 stars 2 forks source link

Per package dependency versioning via `vim.dep` #3

Open tjdevries opened 2 years ago

tjdevries commented 2 years ago
-- Usage:
local path = vim.dep "plenary.path"

Implementation:

-- instead of deps, we can call it `lua_modules` :cry:
--
-- TODO: Add `deps` stuff to `package.path` so then you could drop all of this in deps/
-- deps/
--  myplugin__plenary__commit

-- Could also add:
-- $ROOT/deps/
-- $ROOT/lua/deps/...
-- For now this works with $ROOT/lua/deps since then `require` bits get to work for free.
-- if we add a new searcher, then it would probably work fine either way.

local uv = vim.loop

-- YOU CAN ONLY CALL THIS FUNCTION FROM THE APPROPRIATE LEVEL OF NESTEDNESS
-- OTHERWISE YOU GET THE WRONG PATH. CAREFUL OF THIS ONE!
local function get_script_path()
  local path = debug.getinfo(3, "S").source:sub(2):match "(.*/)"
  return uv.fs_realpath(path)
end

local function find_plugin_root(path)
  -- TODO: Windows Sadge
  local parts = vim.split(path, "/")
  while #parts > 0 do
    local subpath = table.concat(parts, "/")
    if uv.fs_stat(subpath .. "/plugin.lua") then
      return subpath
    end

    table.remove(parts, #parts)
  end

  return nil
end

local function get_dep_name(name)
  name = string.gsub(name, "/", ".")
  return vim.split(name, "%.")[1]
end

local function make_dep_path(plug_name, dep_name, version, name)
  return string.format("deps.%s__%s__%s.lua.%s", plug_name, dep_name, version, name)
end

local dep = function(name)
  local plugin_script = get_script_path()
  local dep_name = get_dep_name(name)

  local plugin_root = find_plugin_root(plugin_script)
  if plugin_root then
    local plugin_config = loadfile(plugin_root .. "/plugin.lua")()
    local plugin_deps = plugin_config.dependencies
    if not plugin_deps[dep_name] then
      error(string.format("Could not find dependency: %s // %s", dep_name, name))
    end

    -- TODO: This is a bit goofy, but it makes it so that we can
    -- still use require easily.
    local dep_version = string.gsub(plugin_deps[dep_name], "%.", "-")
    local dep_path = make_dep_path(plugin_config.name, dep_name, dep_version, name)

    NESTED_REQUIRE = true
    local ok, mod = pcall(require, dep_path)
    NESTED_REQUIRE = false

    if not ok then
      error(mod)
    end

    return mod
    -- return setfenv(require, {
    --   require = function(req_name)
    --     error(req_name)
    --   end,
    -- })(dep_path)
  end

  error(string.format("Unable to find plugin root: %s // %s", name, plugin_script))
end

vim.dep = dep
ii14 commented 2 years ago

The idea is to have multiple versions of the same package? I think in order for that to be a thing, dependencies would have to explicitly declare that they promise not to modify the global state in any way, so you don't have multiple versions fighting over the same global variable or something like that.