diff options
| author | William Boman <william@redwill.se> | 2022-07-08 18:34:38 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-08 18:34:38 +0200 |
| commit | 976aa4fbee8a070f362cab6f6ec84e9251a90cf9 (patch) | |
| tree | 5e8d9c9c59444a25c7801b8f39763c4ba6e1f76d /lua/mason-core/package | |
| parent | feat: add gotests, gomodifytags, impl (#28) (diff) | |
| download | mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.gz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.bz2 mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.lz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.xz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.zst mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.zip | |
refactor: add mason-schemas and mason-core modules (#29)
* refactor: add mason-schemas and move generated filetype map to mason-lspconfig
* refactor: add mason-core module
Diffstat (limited to 'lua/mason-core/package')
| -rw-r--r-- | lua/mason-core/package/init.lua | 205 | ||||
| -rw-r--r-- | lua/mason-core/package/version-check.lua | 91 |
2 files changed, 296 insertions, 0 deletions
diff --git a/lua/mason-core/package/init.lua b/lua/mason-core/package/init.lua new file mode 100644 index 00000000..631e423d --- /dev/null +++ b/lua/mason-core/package/init.lua @@ -0,0 +1,205 @@ +local registry = require "mason-registry" +local a = require "mason-core.async" +local _ = require "mason-core.functional" +local installer = require "mason-core.installer" +local InstallationHandle = require "mason-core.installer.handle" +local Optional = require "mason-core.optional" +local log = require "mason-core.log" +local EventEmitter = require "mason-core.EventEmitter" +local receipt = require "mason-core.receipt" +local fs = require "mason-core.fs" +local path = require "mason-core.path" +local linker = require "mason-core.installer.linker" + +local version_checks = require "mason-core.package.version-check" + +---@class Package : EventEmitter +---@field name string +---@field spec PackageSpec +---@field private handle InstallHandle @The currently associated handle. +local Package = setmetatable({}, { __index = EventEmitter }) + +---@param package_identifier string +---@return string, string | nil +Package.Parse = function(package_identifier) + local name, version = unpack(vim.split(package_identifier, "@")) + return name, version +end + +---@alias PackageLanguage string + +---@type table<PackageLanguage, PackageLanguage> +Package.Lang = setmetatable({}, { + __index = function(s, lang) + s[lang] = lang + return s[lang] + end, +}) + +---@class PackageCategory +Package.Cat = { + Compiler = "Compiler", + Runtime = "Runtime", + DAP = "DAP", + LSP = "LSP", + Linter = "Linter", + Formatter = "Formatter", +} + +local PackageMt = { __index = Package } + +---@class PackageSpec +---@field name string +---@field desc string +---@field homepage string +---@field categories PackageCategory[] +---@field languages PackageLanguage[] +---@field install async fun(ctx: InstallContext) + +---@param spec PackageSpec +function Package.new(spec) + vim.validate { + name = { spec.name, "s" }, + desc = { spec.desc, "s" }, + homepage = { spec.homepage, "s" }, + categories = { spec.categories, "t" }, + languages = { spec.languages, "t" }, + install = { spec.install, "f" }, + } + + 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 handle = InstallationHandle.new(self) + self.handle = handle + self:emit("handle", handle) + return handle +end + +---@param opts { version: string|nil } | nil +---@return InstallHandle +function Package:install(opts) + opts = opts or {} + return self + :get_handle() + :map(function(handle) + if not handle:is_closed() then + log.fmt_debug("Handle %s already exist for package %s", handle, self) + return handle + end + end) + :or_else_get(function() + local handle = self:new_handle() + -- This function is not expected to be run in async scope, so we create + -- a new scope here and handle the result callback-style. + a.run( + installer.execute, + ---@param success boolean + ---@param result Result + function(success, result) + if not success then + log.error("Unexpected error", result) + self:emit("install:failed", handle) + return + end + result + :on_success(function() + self:emit("install:success", handle) + registry:emit("package:install:success", self, handle) + end) + :on_failure(function() + self:emit("install:failed", handle) + registry:emit("package:install:failed", self, handle) + end) + end, + handle, + { + requested_version = opts.version, + } + ) + return handle + end) +end + +function Package:uninstall() + local was_unlinked = self:unlink() + if was_unlinked then + self:emit "uninstall:success" + end + return was_unlinked +end + +function Package:unlink() + log.fmt_info("Unlinking %s", self) + local install_path = self:get_install_path() + -- 1. Unlink + self:get_receipt():map(_.prop "links"):if_present(function(links) + linker.unlink(self, links) + end) + + -- 2. Remove installation artifacts + if fs.sync.dir_exists(install_path) then + fs.sync.rmrf(install_path) + return true + end + return false +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<@see InstallReceipt> +function Package:get_receipt() + local receipt_path = path.concat { self:get_install_path(), "mason-receipt.json" } + if fs.sync.file_exists(receipt_path) then + return Optional.of(receipt.InstallReceipt.from_json(vim.json.decode(fs.sync.read_file(receipt_path)))) + end + return Optional.empty() +end + +---@param callback fun(success: boolean, version_or_err: string) +function Package:get_installed_version(callback) + a.run(function() + local receipt = self:get_receipt():or_else_throw "Unable to get receipt." + return version_checks.get_installed_version(receipt, self:get_install_path()):get_or_throw() + end, callback) +end + +---@param callback fun(success: boolean, result_or_err: NewPackageVersion) +function Package:check_new_version(callback) + a.run(function() + local receipt = self:get_receipt():or_else_throw "Unable to get receipt." + return version_checks.get_new_version(receipt, self:get_install_path()):get_or_throw() + end, callback) +end + +function Package:get_lsp_settings_schema() + local ok, schema = pcall(require, ("mason-schemas.lsp.%s"):format(self.name)) + if not ok then + return Optional.empty() + end + return Optional.of(schema) +end + +function PackageMt.__tostring(self) + return ("Package(name=%s)"):format(self.name) +end + +return Package diff --git a/lua/mason-core/package/version-check.lua b/lua/mason-core/package/version-check.lua new file mode 100644 index 00000000..b999c280 --- /dev/null +++ b/lua/mason-core/package/version-check.lua @@ -0,0 +1,91 @@ +local Result = require "mason-core.result" +local cargo = require "mason-core.managers.cargo" +local composer = require "mason-core.managers.composer" +local eclipse = require "mason-core.clients.eclipse" +local gem = require "mason-core.managers.gem" +local git = require "mason-core.managers.git" +local github = require "mason-core.managers.github" +local go = require "mason-core.managers.go" +local luarocks = require "mason-core.managers.luarocks" +local npm = require "mason-core.managers.npm" +local pip3 = require "mason-core.managers.pip3" + +---@param field_name string +local function version_in_receipt(field_name) + ---@param receipt InstallReceipt + ---@return Result + return function(receipt) + return Result.success(receipt.primary_source[field_name]) + end +end + +---@type table<InstallReceiptSourceType, async fun(receipt: InstallReceipt, install_dir: string): Result> +local get_installed_version_by_type = { + ["npm"] = npm.get_installed_primary_package_version, + ["pip3"] = pip3.get_installed_primary_package_version, + ["gem"] = gem.get_installed_primary_package_version, + ["cargo"] = cargo.get_installed_primary_package_version, + ["composer"] = composer.get_installed_primary_package_version, + ["git"] = git.get_installed_revision, + ["go"] = go.get_installed_primary_package_version, + ["luarocks"] = luarocks.get_installed_primary_package_version, + ["github_release_file"] = version_in_receipt "release", + ["github_release"] = version_in_receipt "release", + ["github_tag"] = version_in_receipt "tag", + ["jdtls"] = version_in_receipt "version", +} + +---@async +---@param receipt InstallReceipt +local function jdtls_check(receipt) + return eclipse.fetch_latest_jdtls_version():map_catching(function(latest_version) + if receipt.primary_source.version ~= latest_version then + return { + name = "jdtls", + current_version = receipt.primary_source.version, + latest_version = latest_version, + } + end + error "Primary package is not outdated." + end) +end + +---@class NewPackageVersion +---@field name string +---@field current_version string +---@field latest_version string + +local get_new_version_by_type = { + ["npm"] = npm.check_outdated_primary_package, + ["pip3"] = pip3.check_outdated_primary_package, + ["git"] = git.check_outdated_git_clone, + ["cargo"] = cargo.check_outdated_primary_package, + ["composer"] = composer.check_outdated_primary_package, + ["gem"] = gem.check_outdated_primary_package, + ["go"] = go.check_outdated_primary_package, + ["luarocks"] = luarocks.check_outdated_primary_package, + ["jdtls"] = jdtls_check, + ["github_release_file"] = github.check_outdated_primary_package_release, + ["github_release"] = github.check_outdated_primary_package_release, + ["github_tag"] = github.check_outdated_primary_package_tag, +} + +---@param provider_mapping table<string, async fun(receipt: InstallReceipt, install_dir: string)>: Result +local function version_check(provider_mapping) + ---@param receipt InstallReceipt + ---@param install_dir string + return function(receipt, install_dir) + local check = provider_mapping[receipt.primary_source.type] + if not check then + return Result.failure( + ("Packages installed via %s does not yet support version check."):format(receipt.primary_source.type) + ) + end + return check(receipt, install_dir) + end +end + +return { + get_installed_version = version_check(get_installed_version_by_type), + get_new_version = version_check(get_new_version_by_type), +} |
