aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core/package
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2022-07-08 18:34:38 +0200
committerGitHub <noreply@github.com>2022-07-08 18:34:38 +0200
commit976aa4fbee8a070f362cab6f6ec84e9251a90cf9 (patch)
tree5e8d9c9c59444a25c7801b8f39763c4ba6e1f76d /lua/mason-core/package
parentfeat: add gotests, gomodifytags, impl (#28) (diff)
downloadmason-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.lua205
-rw-r--r--lua/mason-core/package/version-check.lua91
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),
+}