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 25, 2023
1 parent 7185173 commit 1c78cf7
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 59 deletions.
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.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)
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
29 changes: 11 additions & 18 deletions lua/mason/ui/instance.lua
Original file line number Diff line number Diff line change
Expand Up @@ -627,29 +627,17 @@ local registered_packages = {}

---@param pkg Package
local function setup_package(pkg)
if registered_packages[pkg] then
return
end

mutate_state(function(state)
for _, group in ipairs { state.packages.installed, state.packages.uninstalled, state.packages.failed } do
for i, existing_pkg in ipairs(group) do
if existing_pkg.name == pkg.name and pkg ~= existing_pkg then
-- New package instance (i.e. from a new, updated, registry source).
-- Release the old package instance.
table.remove(group, i)
end
end
end
end)

-- hydrate initial state
mutate_state(function(state)
state.packages.states[pkg.name] = create_initial_package_state()
state.packages.visible[pkg.name] = true
table.insert(state.packages[pkg:is_installed() and "installed" or "uninstalled"], pkg)
end)

if registered_packages[pkg] then
return
end

pkg:get_handle():if_present(setup_handle)
pkg:on("handle", setup_handle)

Expand Down Expand Up @@ -715,14 +703,19 @@ local function setup_packages(packages)
end

setup_packages(registry.get_all_packages())
update_registry_info()

registry:on("update", function()
mutate_state(function(state)
state.packages.installed = {}
state.packages.failed = {}
state.packages.uninstalled = {}
end)

setup_packages(registry.get_all_packages())
update_registry_info()
end)

update_registry_info()

window.init {
effects = effects,
border = settings.current.ui.border,
Expand Down

0 comments on commit 1c78cf7

Please sign in to comment.