-
-
Notifications
You must be signed in to change notification settings - Fork 282
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(registry): add file: source protocol
- Loading branch information
1 parent
b5bb138
commit 56728a1
Showing
4 changed files
with
196 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |