aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core/installer/registry/init.lua
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2023-03-12 08:21:15 +0100
committerGitHub <noreply@github.com>2023-03-12 08:21:15 +0100
commita01d02ad7f680aec98a1e2ec35b04cedd307cfa8 (patch)
tree1a09e274a1f2a4da85b911abcbb182a211035501 /lua/mason-core/installer/registry/init.lua
parentfeat(golangci-lint): support linux_arm64 (#1089) (diff)
downloadmason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar
mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.gz
mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.bz2
mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.lz
mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.xz
mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.tar.zst
mason-a01d02ad7f680aec98a1e2ec35b04cedd307cfa8.zip
feat: add github registry source capabilities (#1091)
Diffstat (limited to 'lua/mason-core/installer/registry/init.lua')
-rw-r--r--lua/mason-core/installer/registry/init.lua196
1 files changed, 196 insertions, 0 deletions
diff --git a/lua/mason-core/installer/registry/init.lua b/lua/mason-core/installer/registry/init.lua
new file mode 100644
index 00000000..7c27f1ef
--- /dev/null
+++ b/lua/mason-core/installer/registry/init.lua
@@ -0,0 +1,196 @@
+local a = require "mason-core.async"
+local Result = require "mason-core.result"
+local _ = require "mason-core.functional"
+local Purl = require "mason-core.purl"
+local Optional = require "mason-core.optional"
+local link = require "mason-core.installer.registry.link"
+local log = require "mason-core.log"
+local semver = require "mason-core.semver"
+
+local M = {}
+
+M.SCHEMA_CAP = _.set_of {
+ "registry+v1",
+}
+
+---@type table<string, InstallerProvider>
+local PROVIDERS = {}
+
+---@param id string
+---@param provider InstallerProvider
+function M.register_provider(id, provider)
+ PROVIDERS[id] = provider
+end
+
+M.register_provider("cargo", _.lazy_require "mason-core.installer.registry.providers.cargo")
+M.register_provider("composer", _.lazy_require "mason-core.installer.registry.providers.composer")
+M.register_provider("gem", _.lazy_require "mason-core.installer.registry.providers.gem")
+M.register_provider("generic", _.lazy_require "mason-core.installer.registry.providers.generic")
+M.register_provider("github", _.lazy_require "mason-core.installer.registry.providers.github")
+M.register_provider("golang", _.lazy_require "mason-core.installer.registry.providers.golang")
+M.register_provider("luarocks", _.lazy_require "mason-core.installer.registry.providers.luarocks")
+M.register_provider("npm", _.lazy_require "mason-core.installer.registry.providers.npm")
+M.register_provider("nuget", _.lazy_require "mason-core.installer.registry.providers.nuget")
+M.register_provider("opam", _.lazy_require "mason-core.installer.registry.providers.opam")
+M.register_provider("pypi", _.lazy_require "mason-core.installer.registry.providers.pypi")
+
+---@param purl Purl
+local function get_provider(purl)
+ return Optional.of_nilable(PROVIDERS[purl.type]):ok_or(("Unknown purl type: %s"):format(purl.type))
+end
+
+---@class InstallerProvider
+---@field parse fun(source: RegistryPackageSource, purl: Purl, opts: PackageInstallOpts): Result
+---@field install async fun(ctx: InstallContext, source: ParsedPackageSource): Result
+
+---@class ParsedPackageSource
+
+---Upserts {dst} with contents of {src}. List table values will be merged, with contents of {src} prepended.
+---@param dst table
+---@param src table
+local function upsert(dst, src)
+ for k, v in pairs(src) do
+ if type(v) == "table" then
+ if vim.tbl_islist(v) then
+ dst[k] = _.concat(v, dst[k] or {})
+ else
+ dst[k] = upsert(dst[k] or {}, src[k])
+ end
+ else
+ dst[k] = v
+ end
+ end
+ return dst
+end
+
+---@param source RegistryPackageSource
+---@param version string
+local function coalesce_source(source, version)
+ if source.version_overrides then
+ for i = #source.version_overrides, 1, -1 do
+ local version_override = source.version_overrides[i]
+ local version_type, constraint = unpack(_.split(":", version_override.constraint))
+ if version_type == "semver" then
+ local version_match = Result.try(function(try)
+ local requested_version = try(semver.parse(version))
+ if _.starts_with("<=", constraint) then
+ local rule_version = try(semver.parse(_.strip_prefix("<=", constraint)))
+ return requested_version <= rule_version
+ elseif _.starts_with(">=", constraint) then
+ local rule_version = try(semver.parse(_.strip_prefix(">=", constraint)))
+ return requested_version >= rule_version
+ else
+ local rule_version = try(semver.parse(constraint))
+ return requested_version == rule_version
+ end
+ end):get_or_else(false)
+
+ if version_match then
+ if version_override.id then
+ -- Because this entry provides its own purl id, it overrides the entire source definition.
+ return version_override
+ else
+ -- Upsert the default source with the contents of the version override.
+ return upsert(vim.deepcopy(source), _.dissoc("constraint", version_override))
+ end
+ end
+ end
+ end
+ end
+ return source
+end
+
+---@param spec RegistryPackageSpec
+---@param opts PackageInstallOpts
+function M.parse(spec, opts)
+ log.debug("Parsing spec", spec.name, opts)
+ return Result.try(function(try)
+ if not M.SCHEMA_CAP[spec.schema] then
+ return Result.failure(
+ ("Current version of mason.nvim is not capable of parsing package schema version %q."):format(
+ spec.schema
+ )
+ )
+ end
+
+ local source = opts.version and coalesce_source(spec.source, opts.version) or spec.source
+
+ ---@type Purl
+ local purl = try(Purl.parse(source.id))
+ log.trace("Parsed purl.", source.id, purl)
+ if opts.version then
+ purl.version = opts.version
+ end
+
+ ---@type InstallerProvider
+ local provider = try(get_provider(purl))
+ log.trace("Found provider for purl.", source.id)
+ local parsed_source = try(provider.parse(source, purl, opts))
+ log.trace("Parsed source for purl.", source.id, parsed_source)
+ return {
+ provider = provider,
+ source = parsed_source,
+ purl = purl,
+ }
+ end)
+end
+
+---@async
+---@param spec RegistryPackageSpec
+---@param opts PackageInstallOpts
+function M.compile(spec, opts)
+ log.debug("Compiling installer.", spec.name, opts)
+ return Result.try(function(try)
+ if vim.in_fast_event() then
+ -- Parsers run synchronously and may access API functions, so we schedule before-hand.
+ a.scheduler()
+ end
+
+ local map_parse_err = _.cond {
+ {
+ _.equals "PLATFORM_UNSUPPORTED",
+ function()
+ if opts.target then
+ return ("Platform %q is unsupported."):format(opts.target)
+ else
+ return "The current platform is unsupported."
+ end
+ end,
+ },
+ { _.T, _.identity },
+ }
+
+ ---@type { purl: Purl, provider: InstallerProvider, source: ParsedPackageSource }
+ local parsed = try(M.parse(spec, opts):map_err(map_parse_err))
+
+ ---@async
+ ---@param ctx InstallContext
+ return function(ctx)
+ return Result.try(function(try)
+ -- Run installer
+ try(parsed.provider.install(ctx, parsed.source))
+
+ -- Expand & register links
+ if spec.bin then
+ try(link.bin(ctx, spec, parsed.purl, parsed.source))
+ end
+ if spec.share then
+ try(link.share(ctx, spec, parsed.purl, parsed.source))
+ end
+ if spec.opt then
+ try(link.opt(ctx, spec, parsed.purl, parsed.source))
+ end
+
+ ctx.receipt:with_primary_source {
+ type = ctx.package.spec.schema,
+ id = Purl.compile(parsed.purl),
+ source = parsed.source,
+ }
+ end):on_failure(function(err)
+ error(err, 0)
+ end)
+ end
+ end)
+end
+
+return M