diff options
| author | William Boman <william@redwill.se> | 2023-03-12 08:21:15 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-12 08:21:15 +0100 |
| commit | a01d02ad7f680aec98a1e2ec35b04cedd307cfa8 (patch) | |
| tree | 1a09e274a1f2a4da85b911abcbb182a211035501 /lua/mason-core/installer/registry/init.lua | |
| parent | feat(golangci-lint): support linux_arm64 (#1089) (diff) | |
| download | mason-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.lua | 196 |
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 |
