Skip to content

Commit

Permalink
feat(registry): add file: source protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
williamboman committed Aug 17, 2023
1 parent b5bb138 commit 56728a1
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 38 deletions.
144 changes: 144 additions & 0 deletions lua/mason-registry/sources/file.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
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 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: table<string, 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 vim.tbl_values(self:get_buffer().specs)
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 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.success {}
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)
local index_by_name = _.index_by(_.prop "name")
local instances = _.compose(
index_by_name,
_.map(util.hydrate_package(self.buffer and self.buffer.instances or {})),
_.filter_map(util.map_registry_spec)
)(specs)

self.buffer = {
specs = index_by_name(specs),
instances = instances,
}
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
43 changes: 5 additions & 38 deletions lua/mason-registry/sources/github.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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 +53,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

0 comments on commit 56728a1

Please sign in to comment.