From 308a4cf5fd9ed79dd57243f0290e317951cd2361 Mon Sep 17 00:00:00 2001 From: William Boman Date: Wed, 11 Oct 2023 22:47:18 +0200 Subject: chore: hoist single file modules --- lua/mason-core/package.lua | 274 ++++++++++++++++++++++++++++++++++++++ lua/mason-core/package/init.lua | 274 -------------------------------------- lua/mason-core/providers.lua | 107 +++++++++++++++ lua/mason-core/providers/init.lua | 107 --------------- 4 files changed, 381 insertions(+), 381 deletions(-) create mode 100644 lua/mason-core/package.lua delete mode 100644 lua/mason-core/package/init.lua create mode 100644 lua/mason-core/providers.lua delete mode 100644 lua/mason-core/providers/init.lua (limited to 'lua') diff --git a/lua/mason-core/package.lua b/lua/mason-core/package.lua new file mode 100644 index 00000000..b0da8a61 --- /dev/null +++ b/lua/mason-core/package.lua @@ -0,0 +1,274 @@ +local EventEmitter = require "mason-core.EventEmitter" +local InstallLocation = require "mason-core.installer.location" +local InstallRunner = require "mason-core.installer.runner" +local Optional = require "mason-core.optional" +local Purl = require "mason-core.purl" +local Result = require "mason-core.result" +local _ = require "mason-core.functional" +local fs = require "mason-core.fs" +local log = require "mason-core.log" +local path = require "mason-core.path" +local platform = require "mason-core.platform" +local registry = require "mason-registry" +local settings = require "mason.settings" +local Semaphore = require("mason-core.async.control").Semaphore + +---@class Package : EventEmitter +---@field name string +---@field spec RegistryPackageSpec +---@field private handle InstallHandle The currently associated handle. +local Package = setmetatable({}, { __index = EventEmitter }) + +---@param package_identifier string +---@return string, string? +Package.Parse = function(package_identifier) + local name, version = unpack(vim.split(package_identifier, "@")) + return name, version +end + +---@alias PackageLanguage string + +---@type table +Package.Lang = setmetatable({}, { + __index = function(s, lang) + s[lang] = lang + return s[lang] + end, +}) + +---@enum PackageCategory +Package.Cat = { + Compiler = "Compiler", + Runtime = "Runtime", + DAP = "DAP", + LSP = "LSP", + Linter = "Linter", + Formatter = "Formatter", +} + +---@alias PackageLicense string + +---@type table +Package.License = setmetatable({}, { + __index = function(s, license) + s[license] = license + return s[license] + end, +}) + +local PackageMt = { __index = Package } + +---@class RegistryPackageSourceVersionOverride : RegistryPackageSource +---@field constraint string + +---@class RegistryPackageSource +---@field id string PURL-compliant identifier. +---@field version_overrides? RegistryPackageSourceVersionOverride[] + +---@class RegistryPackageSchemas +---@field lsp string? + +---@class RegistryPackageDeprecation +---@field since string +---@field message string + +---@alias RegistryPackageSpecSchema +--- | '"registry+v1"' + +---@class RegistryPackageSpec +---@field schema RegistryPackageSpecSchema +---@field name string +---@field description string +---@field homepage string +---@field licenses string[] +---@field languages string[] +---@field categories string[] +---@field source RegistryPackageSource +---@field deprecation RegistryPackageDeprecation? +---@field schemas RegistryPackageSchemas? +---@field bin table? +---@field share table? +---@field opt table? + +---@param spec RegistryPackageSpec +local function validate_spec(spec) + if platform.cached_features["nvim-0.11"] ~= 1 then + return + end + vim.validate("schema", spec.schema, _.equals "registry+v1", "registry+v1") + vim.validate("name", spec.name, "string") + vim.validate("description", spec.description, "string") + vim.validate("homepage", spec.homepage, "string") + vim.validate("licenses", spec.licenses, "table") + vim.validate("categories", spec.categories, "table") + vim.validate("languages", spec.languages, "table") + vim.validate("source", spec.source, "table") + vim.validate("bin", spec.bin, { "table", "nil" }) + vim.validate("share", spec.share, { "table", "nil" }) +end + +---@param spec RegistryPackageSpec +function Package.new(spec) + validate_spec(spec) + return EventEmitter.init(setmetatable({ + name = spec.name, -- for convenient access + spec = spec, + }, PackageMt)) +end + +function Package:new_handle() + self:get_handle():if_present(function(handle) + assert(handle:is_closed(), "Cannot create new handle because existing handle is not closed.") + end) + log.fmt_trace("Creating new handle for %s", self) + local InstallationHandle = require "mason-core.installer.handle" + local handle = InstallationHandle.new(self) + self.handle = handle + + -- Ideally we'd decouple this and leverage Mason's event system, but to allow loading as little as possible during + -- setup (i.e. not load modules related to Mason's event system) of the mason.nvim plugin we explicitly call into + -- terminator here. + require("mason-core.terminator").register(handle) + + self:emit("handle", handle) + registry:emit("package:handle", self, handle) + + return handle +end + +---@alias PackageInstallOpts { version?: string, debug?: boolean, target?: string, force?: boolean, strict?: boolean } + +-- TODO this needs to be elsewhere +local semaphore = Semaphore.new(settings.current.max_concurrent_installers) + +function Package:is_installing() + return self:get_handle() + :map( + ---@param handle InstallHandle + function(handle) + return not handle:is_closed() + end + ) + :or_else(false) +end + +---@param opts? PackageInstallOpts +---@param callback? fun(success: boolean, result: any) +---@return InstallHandle +function Package:install(opts, callback) + opts = opts or {} + assert(not self:is_installing(), "Package is already installing.") + local handle = self:new_handle() + local runner = InstallRunner.new(InstallLocation.new(settings.current.install_root_dir), handle, semaphore) + runner:execute(opts, callback) + return handle +end + +---@return boolean +function Package:uninstall() + return self:get_receipt() + :map(function(receipt) + self:unlink(receipt) + self:emit("uninstall:success", receipt) + registry:emit("package:uninstall:success", self, receipt) + return true + end) + :or_else(false) +end + +---@private +---@param receipt InstallReceipt +function Package:unlink(receipt) + log.fmt_trace("Unlinking %s", self) + local install_path = self:get_install_path() + + -- 1. Unlink + local linker = require "mason-core.installer.linker" + linker.unlink(self, receipt):get_or_throw() + + -- 2. Remove installation artifacts + fs.sync.rmrf(install_path) +end + +function Package:is_installed() + return registry.is_installed(self.name) +end + +function Package:get_handle() + return Optional.of_nilable(self.handle) +end + +function Package:get_install_path() + return path.package_prefix(self.name) +end + +---@return Optional # Optional +function Package:get_receipt() + local receipt_path = path.concat { self:get_install_path(), "mason-receipt.json" } + if fs.sync.file_exists(receipt_path) then + local receipt = require "mason-core.receipt" + return Optional.of(receipt.InstallReceipt.from_json(vim.json.decode(fs.sync.read_file(receipt_path)))) + end + return Optional.empty() +end + +---@return string? +function Package:get_installed_version() + return self:get_receipt() + :and_then( + ---@param receipt InstallReceipt + function(receipt) + local source = receipt:get_source() + if source.id then + return Purl.parse(source.id):map(_.prop "version"):ok() + else + return Optional.empty() + end + end + ) + :or_else(nil) +end + +---@return string +function Package:get_latest_version() + return Purl.parse(self.spec.source.id) + :map(_.prop "version") + :get_or_throw(("Unable to retrieve version from malformed purl: %s."):format(self.spec.source.id)) +end + +---@param opts? PackageInstallOpts +function Package:is_installable(opts) + return require("mason-core.installer.compiler").parse(self.spec, opts or {}):is_success() +end + +---@return Result # Result +function Package:get_all_versions() + local compiler = require "mason-core.installer.compiler" + return Result.try(function(try) + ---@type Purl + local purl = try(Purl.parse(self.spec.source.id)) + ---@type InstallerCompiler + local compiler = try(compiler.get_compiler(purl)) + return compiler.get_versions(purl, self.spec.source) + end) +end + +function Package:get_lsp_settings_schema() + local schema_file = path.share_prefix(path.concat { "mason-schemas", "lsp", ("%s.json"):format(self.name) }) + if fs.sync.file_exists(schema_file) then + return Result.pcall(vim.json.decode, fs.sync.read_file(schema_file), { + luanil = { object = true, array = true }, + }):ok() + end + return Optional.empty() +end + +function PackageMt.__tostring(self) + return ("Package(name=%s)"):format(self.name) +end + +function Package:get_aliases() + return require("mason-registry").get_package_aliases(self.name) +end + +return Package diff --git a/lua/mason-core/package/init.lua b/lua/mason-core/package/init.lua deleted file mode 100644 index b0da8a61..00000000 --- a/lua/mason-core/package/init.lua +++ /dev/null @@ -1,274 +0,0 @@ -local EventEmitter = require "mason-core.EventEmitter" -local InstallLocation = require "mason-core.installer.location" -local InstallRunner = require "mason-core.installer.runner" -local Optional = require "mason-core.optional" -local Purl = require "mason-core.purl" -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local fs = require "mason-core.fs" -local log = require "mason-core.log" -local path = require "mason-core.path" -local platform = require "mason-core.platform" -local registry = require "mason-registry" -local settings = require "mason.settings" -local Semaphore = require("mason-core.async.control").Semaphore - ----@class Package : EventEmitter ----@field name string ----@field spec RegistryPackageSpec ----@field private handle InstallHandle The currently associated handle. -local Package = setmetatable({}, { __index = EventEmitter }) - ----@param package_identifier string ----@return string, string? -Package.Parse = function(package_identifier) - local name, version = unpack(vim.split(package_identifier, "@")) - return name, version -end - ----@alias PackageLanguage string - ----@type table -Package.Lang = setmetatable({}, { - __index = function(s, lang) - s[lang] = lang - return s[lang] - end, -}) - ----@enum PackageCategory -Package.Cat = { - Compiler = "Compiler", - Runtime = "Runtime", - DAP = "DAP", - LSP = "LSP", - Linter = "Linter", - Formatter = "Formatter", -} - ----@alias PackageLicense string - ----@type table -Package.License = setmetatable({}, { - __index = function(s, license) - s[license] = license - return s[license] - end, -}) - -local PackageMt = { __index = Package } - ----@class RegistryPackageSourceVersionOverride : RegistryPackageSource ----@field constraint string - ----@class RegistryPackageSource ----@field id string PURL-compliant identifier. ----@field version_overrides? RegistryPackageSourceVersionOverride[] - ----@class RegistryPackageSchemas ----@field lsp string? - ----@class RegistryPackageDeprecation ----@field since string ----@field message string - ----@alias RegistryPackageSpecSchema ---- | '"registry+v1"' - ----@class RegistryPackageSpec ----@field schema RegistryPackageSpecSchema ----@field name string ----@field description string ----@field homepage string ----@field licenses string[] ----@field languages string[] ----@field categories string[] ----@field source RegistryPackageSource ----@field deprecation RegistryPackageDeprecation? ----@field schemas RegistryPackageSchemas? ----@field bin table? ----@field share table? ----@field opt table? - ----@param spec RegistryPackageSpec -local function validate_spec(spec) - if platform.cached_features["nvim-0.11"] ~= 1 then - return - end - vim.validate("schema", spec.schema, _.equals "registry+v1", "registry+v1") - vim.validate("name", spec.name, "string") - vim.validate("description", spec.description, "string") - vim.validate("homepage", spec.homepage, "string") - vim.validate("licenses", spec.licenses, "table") - vim.validate("categories", spec.categories, "table") - vim.validate("languages", spec.languages, "table") - vim.validate("source", spec.source, "table") - vim.validate("bin", spec.bin, { "table", "nil" }) - vim.validate("share", spec.share, { "table", "nil" }) -end - ----@param spec RegistryPackageSpec -function Package.new(spec) - validate_spec(spec) - return EventEmitter.init(setmetatable({ - name = spec.name, -- for convenient access - spec = spec, - }, PackageMt)) -end - -function Package:new_handle() - self:get_handle():if_present(function(handle) - assert(handle:is_closed(), "Cannot create new handle because existing handle is not closed.") - end) - log.fmt_trace("Creating new handle for %s", self) - local InstallationHandle = require "mason-core.installer.handle" - local handle = InstallationHandle.new(self) - self.handle = handle - - -- Ideally we'd decouple this and leverage Mason's event system, but to allow loading as little as possible during - -- setup (i.e. not load modules related to Mason's event system) of the mason.nvim plugin we explicitly call into - -- terminator here. - require("mason-core.terminator").register(handle) - - self:emit("handle", handle) - registry:emit("package:handle", self, handle) - - return handle -end - ----@alias PackageInstallOpts { version?: string, debug?: boolean, target?: string, force?: boolean, strict?: boolean } - --- TODO this needs to be elsewhere -local semaphore = Semaphore.new(settings.current.max_concurrent_installers) - -function Package:is_installing() - return self:get_handle() - :map( - ---@param handle InstallHandle - function(handle) - return not handle:is_closed() - end - ) - :or_else(false) -end - ----@param opts? PackageInstallOpts ----@param callback? fun(success: boolean, result: any) ----@return InstallHandle -function Package:install(opts, callback) - opts = opts or {} - assert(not self:is_installing(), "Package is already installing.") - local handle = self:new_handle() - local runner = InstallRunner.new(InstallLocation.new(settings.current.install_root_dir), handle, semaphore) - runner:execute(opts, callback) - return handle -end - ----@return boolean -function Package:uninstall() - return self:get_receipt() - :map(function(receipt) - self:unlink(receipt) - self:emit("uninstall:success", receipt) - registry:emit("package:uninstall:success", self, receipt) - return true - end) - :or_else(false) -end - ----@private ----@param receipt InstallReceipt -function Package:unlink(receipt) - log.fmt_trace("Unlinking %s", self) - local install_path = self:get_install_path() - - -- 1. Unlink - local linker = require "mason-core.installer.linker" - linker.unlink(self, receipt):get_or_throw() - - -- 2. Remove installation artifacts - fs.sync.rmrf(install_path) -end - -function Package:is_installed() - return registry.is_installed(self.name) -end - -function Package:get_handle() - return Optional.of_nilable(self.handle) -end - -function Package:get_install_path() - return path.package_prefix(self.name) -end - ----@return Optional # Optional -function Package:get_receipt() - local receipt_path = path.concat { self:get_install_path(), "mason-receipt.json" } - if fs.sync.file_exists(receipt_path) then - local receipt = require "mason-core.receipt" - return Optional.of(receipt.InstallReceipt.from_json(vim.json.decode(fs.sync.read_file(receipt_path)))) - end - return Optional.empty() -end - ----@return string? -function Package:get_installed_version() - return self:get_receipt() - :and_then( - ---@param receipt InstallReceipt - function(receipt) - local source = receipt:get_source() - if source.id then - return Purl.parse(source.id):map(_.prop "version"):ok() - else - return Optional.empty() - end - end - ) - :or_else(nil) -end - ----@return string -function Package:get_latest_version() - return Purl.parse(self.spec.source.id) - :map(_.prop "version") - :get_or_throw(("Unable to retrieve version from malformed purl: %s."):format(self.spec.source.id)) -end - ----@param opts? PackageInstallOpts -function Package:is_installable(opts) - return require("mason-core.installer.compiler").parse(self.spec, opts or {}):is_success() -end - ----@return Result # Result -function Package:get_all_versions() - local compiler = require "mason-core.installer.compiler" - return Result.try(function(try) - ---@type Purl - local purl = try(Purl.parse(self.spec.source.id)) - ---@type InstallerCompiler - local compiler = try(compiler.get_compiler(purl)) - return compiler.get_versions(purl, self.spec.source) - end) -end - -function Package:get_lsp_settings_schema() - local schema_file = path.share_prefix(path.concat { "mason-schemas", "lsp", ("%s.json"):format(self.name) }) - if fs.sync.file_exists(schema_file) then - return Result.pcall(vim.json.decode, fs.sync.read_file(schema_file), { - luanil = { object = true, array = true }, - }):ok() - end - return Optional.empty() -end - -function PackageMt.__tostring(self) - return ("Package(name=%s)"):format(self.name) -end - -function Package:get_aliases() - return require("mason-registry").get_package_aliases(self.name) -end - -return Package diff --git a/lua/mason-core/providers.lua b/lua/mason-core/providers.lua new file mode 100644 index 00000000..5e2a8ea0 --- /dev/null +++ b/lua/mason-core/providers.lua @@ -0,0 +1,107 @@ +local Result = require "mason-core.result" +local log = require "mason-core.log" +local settings = require "mason.settings" + +---@alias GitHubRelease { tag_name: string, prerelease: boolean, draft: boolean, assets: table[] } +---@alias GitHubTag { name: string } + +---@class GitHubProvider +---@field get_latest_release? async fun(repo: string): Result # Result +---@field get_all_release_versions? async fun(repo: string): Result # Result +---@field get_latest_tag? async fun(repo: string): Result # Result +---@field get_all_tags? async fun(repo: string): Result # Result + +---@alias NpmPackage { name: string, version: string } + +---@class NpmProvider +---@field get_latest_version? async fun(pkg: string): Result # Result +---@field get_all_versions? async fun(pkg: string): Result # Result + +---@alias PyPiPackage { name: string, version: string } + +---@class PyPiProvider +---@field get_latest_version? async fun(pkg: string): Result # Result +---@field get_all_versions? async fun(pkg: string): Result # Result # Sorting should not be relied upon due to "proprietary" sorting algo in pip that is difficult to replicate in mason-registry-api. +---@field get_supported_python_versions? async fun(pkg: string, version: string): Result # Result # Returns a version specifier as provided by the PyPI API (see PEP440). + +---@alias RubyGem { name: string, version: string } + +---@class RubyGemsProvider +---@field get_latest_version? async fun(gem: string): Result # Result +---@field get_all_versions? async fun(gem: string): Result # Result + +---@alias PackagistPackage { name: string, version: string } + +---@class PackagistProvider +---@field get_latest_version? async fun(pkg: string): Result # Result +---@field get_all_versions? async fun(pkg: string): Result # Result + +---@alias Crate { name: string, version: string } + +---@class CratesProvider +---@field get_latest_version? async fun(crate: string): Result # Result +---@field get_all_versions? async fun(crate: string): Result # Result + +---@class GolangProvider +---@field get_all_versions? async fun(pkg: string): Result # Result + +---@class OpenVSXProvider +---@field get_latest_version? async fun(namespace: string, extension: string): Result # Result +---@field get_all_versions? async fun(namespace: string, extension: string): Result # Result + +---@class Provider +---@field github? GitHubProvider +---@field npm? NpmProvider +---@field pypi? PyPiProvider +---@field rubygems? RubyGemsProvider +---@field packagist? PackagistProvider +---@field crates? CratesProvider +---@field golang? GolangProvider +---@field openvsx? OpenVSXProvider + +local function service_mt(service) + return setmetatable({}, { + __index = function(_, method) + return function(...) + if #settings.current.providers < 1 then + log.error "No providers configured." + return Result.failure "1 or more providers are required." + end + for _, provider_module in ipairs(settings.current.providers) do + local ok, provider = pcall(require, provider_module) + if ok and provider then + local impl = provider[service] and provider[service][method] + if impl then + ---@type boolean, Result + local ok, result = pcall(impl, ...) + if ok and result:is_success() then + return result + else + if getmetatable(result) == Result then + log.fmt_error("Provider %s %s failed: %s", service, method, result:err_or_nil()) + else + log.fmt_error("Provider %s %s errored: %s", service, method, result) + end + end + end + else + log.fmt_error("Unable to find provider %s is not registered. %s", provider_module, provider) + end + end + local err = ("No provider implementation succeeded for %s.%s"):format(service, method) + log.error(err) + return Result.failure(err) + end + end, + }) +end + +---@type Provider +local providers = setmetatable({}, { + __index = function(tbl, service) + tbl[service] = service_mt(service) + return tbl[service] + end, +}) + +return providers diff --git a/lua/mason-core/providers/init.lua b/lua/mason-core/providers/init.lua deleted file mode 100644 index 5e2a8ea0..00000000 --- a/lua/mason-core/providers/init.lua +++ /dev/null @@ -1,107 +0,0 @@ -local Result = require "mason-core.result" -local log = require "mason-core.log" -local settings = require "mason.settings" - ----@alias GitHubRelease { tag_name: string, prerelease: boolean, draft: boolean, assets: table[] } ----@alias GitHubTag { name: string } - ----@class GitHubProvider ----@field get_latest_release? async fun(repo: string): Result # Result ----@field get_all_release_versions? async fun(repo: string): Result # Result ----@field get_latest_tag? async fun(repo: string): Result # Result ----@field get_all_tags? async fun(repo: string): Result # Result - ----@alias NpmPackage { name: string, version: string } - ----@class NpmProvider ----@field get_latest_version? async fun(pkg: string): Result # Result ----@field get_all_versions? async fun(pkg: string): Result # Result - ----@alias PyPiPackage { name: string, version: string } - ----@class PyPiProvider ----@field get_latest_version? async fun(pkg: string): Result # Result ----@field get_all_versions? async fun(pkg: string): Result # Result # Sorting should not be relied upon due to "proprietary" sorting algo in pip that is difficult to replicate in mason-registry-api. ----@field get_supported_python_versions? async fun(pkg: string, version: string): Result # Result # Returns a version specifier as provided by the PyPI API (see PEP440). - ----@alias RubyGem { name: string, version: string } - ----@class RubyGemsProvider ----@field get_latest_version? async fun(gem: string): Result # Result ----@field get_all_versions? async fun(gem: string): Result # Result - ----@alias PackagistPackage { name: string, version: string } - ----@class PackagistProvider ----@field get_latest_version? async fun(pkg: string): Result # Result ----@field get_all_versions? async fun(pkg: string): Result # Result - ----@alias Crate { name: string, version: string } - ----@class CratesProvider ----@field get_latest_version? async fun(crate: string): Result # Result ----@field get_all_versions? async fun(crate: string): Result # Result - ----@class GolangProvider ----@field get_all_versions? async fun(pkg: string): Result # Result - ----@class OpenVSXProvider ----@field get_latest_version? async fun(namespace: string, extension: string): Result # Result ----@field get_all_versions? async fun(namespace: string, extension: string): Result # Result - ----@class Provider ----@field github? GitHubProvider ----@field npm? NpmProvider ----@field pypi? PyPiProvider ----@field rubygems? RubyGemsProvider ----@field packagist? PackagistProvider ----@field crates? CratesProvider ----@field golang? GolangProvider ----@field openvsx? OpenVSXProvider - -local function service_mt(service) - return setmetatable({}, { - __index = function(_, method) - return function(...) - if #settings.current.providers < 1 then - log.error "No providers configured." - return Result.failure "1 or more providers are required." - end - for _, provider_module in ipairs(settings.current.providers) do - local ok, provider = pcall(require, provider_module) - if ok and provider then - local impl = provider[service] and provider[service][method] - if impl then - ---@type boolean, Result - local ok, result = pcall(impl, ...) - if ok and result:is_success() then - return result - else - if getmetatable(result) == Result then - log.fmt_error("Provider %s %s failed: %s", service, method, result:err_or_nil()) - else - log.fmt_error("Provider %s %s errored: %s", service, method, result) - end - end - end - else - log.fmt_error("Unable to find provider %s is not registered. %s", provider_module, provider) - end - end - local err = ("No provider implementation succeeded for %s.%s"):format(service, method) - log.error(err) - return Result.failure(err) - end - end, - }) -end - ----@type Provider -local providers = setmetatable({}, { - __index = function(tbl, service) - tbl[service] = service_mt(service) - return tbl[service] - end, -}) - -return providers -- cgit v1.2.3-70-g09d2