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

feat(registry): add file: source protocol #1457

Merged
merged 1 commit into from
Aug 25, 2023
Merged
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
152 changes: 152 additions & 0 deletions lua/mason-registry/sources/file.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
local Optional = require "mason-core.optional"
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local a = require "mason-core.async"
local async_control = require "mason-core.async.control"
local async_uv = require "mason-core.async.uv"
local fs = require "mason-core.fs"
local log = require "mason-core.log"
local path = require "mason-core.path"
local spawn = require "mason-core.spawn"
local util = require "mason-registry.sources.util"

local Channel = async_control.Channel

---@class FileRegistrySourceSpec
---@field path string

---@class FileRegistrySource : RegistrySource
---@field spec FileRegistrySourceSpec
---@field root_dir string
---@field buffer { specs: RegistryPackageSpec[], instances: table<string, Package> }?
local FileRegistrySource = {}
FileRegistrySource.__index = FileRegistrySource

---@param spec FileRegistrySourceSpec
function FileRegistrySource.new(spec)
return setmetatable({
spec = spec,
}, FileRegistrySource)
end

function FileRegistrySource:is_installed()
return self.buffer ~= nil
end

---@return RegistryPackageSpec[]
function FileRegistrySource:get_all_package_specs()
return _.filter_map(util.map_registry_spec, self:get_buffer().specs)
end

function FileRegistrySource:reload()
if not self:is_installed() then
return
end
self.buffer.instances = _.compose(
_.index_by(_.prop "name"),
_.map(util.hydrate_package(self.buffer.instances or {}))
)(self:get_all_package_specs())
return self.buffer
end

function FileRegistrySource:get_buffer()
return self.buffer or {
specs = {},
instances = {},
}
end

---@param pkg_name string
---@return Package?
function FileRegistrySource:get_package(pkg_name)
return self:get_buffer().instances[pkg_name]
end

function FileRegistrySource:get_all_package_names()
return _.map(_.prop "name", self:get_all_package_specs())
end

function FileRegistrySource:get_installer()
return Optional.of(_.partial(self.install, self))
end

---@async
function FileRegistrySource:install()
return Result.try(function(try)
if vim.fn.executable "yq" ~= 1 then
return Result.failure "yq is not installed."
end
local yq = vim.fn.exepath "yq"

local registry_dir = vim.fn.expand(self.spec.path) --[[@as string]]
local packages_dir = path.concat { registry_dir, "packages" }
if not fs.async.dir_exists(registry_dir) then
return Result.failure(("Directory %s does not exist."):format(registry_dir))
end

if not fs.async.dir_exists(packages_dir) then
return Result.failure "packages/ directory is missing."
end

---@type ReaddirEntry[]
local entries = _.filter(_.prop_eq("type", "directory"), fs.async.readdir(packages_dir))

local channel = Channel.new()
a.run(function()
for _, entry in ipairs(entries) do
channel:send(path.concat { packages_dir, entry.name, "package.yaml" })
end
channel:close()
end, function() end)

local CONSUMERS_COUNT = 10
local consumers = {}
for _ = 1, CONSUMERS_COUNT do
table.insert(consumers, function()
local specs = {}
for package_file in channel:iter() do
local yaml_spec = fs.async.read_file(package_file)
local spec = vim.json.decode(spawn
[yq]({
"-o",
"json",
on_spawn = a.scope(function(_, stdio)
local stdin = stdio[1]
async_uv.write(stdin, yaml_spec)
async_uv.shutdown(stdin)
async_uv.close(stdin)
end),
})
:get_or_throw(("Failed to parse %s."):format(package_file)).stdout)

specs[#specs + 1] = spec
end
return specs
end)
end

local specs = _.reduce(vim.list_extend, {}, _.table_pack(a.wait_all(consumers)))
return specs
end)
:on_success(function(specs)
self.buffer = _.assoc("specs", specs, self.buffer or {})
self:reload()
end)
:on_failure(function(err)
log.fmt_error("Failed to install registry %s. %s", self, err)
end)
end

function FileRegistrySource:get_display_name()
if self:is_installed() then
return ("local: %s"):format(self.spec.path)
else
return ("local: %s [uninstalled]"):format(self.spec.path)
end
end

function FileRegistrySource:__tostring()
return ("FileRegistrySource(path=%s)"):format(self.spec.path)
end

return FileRegistrySource
45 changes: 5 additions & 40 deletions lua/mason-registry/sources/github.lua
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
local Optional = require "mason-core.optional"
local Pkg = require "mason-core.package"
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local fetch = require "mason-core.fetch"
local fs = require "mason-core.fs"
local log = require "mason-core.log"
local path = require "mason-core.path"
local providers = require "mason-core.providers"
local registry_installer = require "mason-core.installer.registry"
local settings = require "mason.settings"
local util = require "mason-registry.sources.util"

-- Parse sha256sum text output to a table<filename: string, sha256sum: string> structure
local parse_checksums = _.compose(_.from_pairs, _.map(_.compose(_.reverse, _.split " ")), _.split "\n", _.trim)
Expand Down Expand Up @@ -52,50 +51,16 @@ function GitHubRegistrySource:get_all_package_specs()
return {}
end
local data = vim.json.decode(fs.sync.read_file(self.data_file)) --[[@as RegistryPackageSpec[] ]]
return _.filter_map(
---@param spec RegistryPackageSpec
function(spec)
-- registry+v1 specifications doesn't include a schema property, so infer it
spec.schema = spec.schema or "registry+v1"

if not registry_installer.SCHEMA_CAP[spec.schema] then
log.fmt_debug("Excluding package=%s with unsupported schema_version=%s", spec.name, spec.schema)
return Optional.empty()
end

-- XXX: this is for compatibilty with the PackageSpec structure
spec.desc = spec.description
return Optional.of(spec)
end,
data
)
return _.filter_map(util.map_registry_spec, data)
end

function GitHubRegistrySource:reload()
if not self:is_installed() then
return
end
self.buffer = _.compose(
_.index_by(_.prop "name"),
_.map(
---@param spec RegistryPackageSpec
function(spec)
-- hydrate Pkg.Lang index
_.each(function(lang)
local _ = Pkg.Lang[lang]
end, spec.languages)

local pkg = self.buffer and self.buffer[spec.name]
if pkg then
-- Apply spec to the existing Package instance. This is important as to not have lingering package
-- instances.
pkg.spec = spec
return pkg
end
return Pkg.new(spec)
end
)
)(self:get_all_package_specs())
self.buffer = _.compose(_.index_by(_.prop "name"), _.map(util.hydrate_package(self.buffer or {})))(
self:get_all_package_specs()
)
return self.buffer
end

Expand Down
7 changes: 7 additions & 0 deletions lua/mason-registry/sources/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ local function parse(registry_id)
mod = id,
}
end
elseif type == "file" then
return function()
local FileRegistrySource = require "mason-registry.sources.file"
return FileRegistrySource.new {
path = id,
}
end
elseif type ~= nil then
error(("Unknown registry type %q: %q."):format(type, registry_id), 0)
end
Expand Down
40 changes: 40 additions & 0 deletions lua/mason-registry/sources/util.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
local Optional = require "mason-core.optional"
local Pkg = require "mason-core.package"
local _ = require "mason-core.functional"
local log = require "mason-core.log"
local registry_installer = require "mason-core.installer.registry"

local M = {}

---@param spec RegistryPackageSpec
function M.map_registry_spec(spec)
spec.schema = spec.schema or "registry+v1"

if not registry_installer.SCHEMA_CAP[spec.schema] then
log.fmt_debug("Excluding package=%s with unsupported schema_version=%s", spec.name, spec.schema)
return Optional.empty()
end

-- XXX: this is for compatibilty with the PackageSpec structure
spec.desc = spec.description
return Optional.of(spec)
end

---@param buffer table<string, Package>
---@param spec RegistryPackageSpec
M.hydrate_package = _.curryN(function(buffer, spec)
-- hydrate Pkg.Lang index
_.each(function(lang)
local _ = Pkg.Lang[lang]
end, spec.languages)

local pkg = buffer[spec.name]
if pkg then
-- Apply spec to the existing Package instance. This is important as to not have lingering package instances.
pkg.spec = spec
return pkg
end
return Pkg.new(spec)
end, 2)

return M
2 changes: 1 addition & 1 deletion lua/mason/api/command.lua
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ end, {
---@param arg_lead string
complete = function(arg_lead)
local registry = require "mason-registry"

registry.refresh()
if _.starts_with("--", arg_lead) then
return _.filter(_.starts_with(arg_lead), {
"--debug",
Expand Down
Loading