diff options
| author | William Boman <william@redwill.se> | 2022-10-05 20:33:21 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-05 20:33:21 +0200 |
| commit | 738684097dfdd9a4a67cd217b0beda3e169bd85d (patch) | |
| tree | d2eb8485dedcac66061b0d99ad7f810828911073 /lua/mason-core/managers | |
| parent | test(cargo): stub crates.io http call (#508) (diff) | |
| download | mason-738684097dfdd9a4a67cd217b0beda3e169bd85d.tar mason-738684097dfdd9a4a67cd217b0beda3e169bd85d.tar.gz mason-738684097dfdd9a4a67cd217b0beda3e169bd85d.tar.bz2 mason-738684097dfdd9a4a67cd217b0beda3e169bd85d.tar.lz mason-738684097dfdd9a4a67cd217b0beda3e169bd85d.tar.xz mason-738684097dfdd9a4a67cd217b0beda3e169bd85d.tar.zst mason-738684097dfdd9a4a67cd217b0beda3e169bd85d.zip | |
feat(cargo): improve handling of git-based crates (#512)
This is all pretty overkill, especially considering the small amount of
packages based on git-based crates.
Diffstat (limited to 'lua/mason-core/managers')
| -rw-r--r-- | lua/mason-core/managers/cargo/init.lua | 167 | ||||
| -rw-r--r-- | lua/mason-core/managers/github/client.lua | 26 |
2 files changed, 143 insertions, 50 deletions
diff --git a/lua/mason-core/managers/cargo/init.lua b/lua/mason-core/managers/cargo/init.lua index b8cd4fa7..8890daf6 100644 --- a/lua/mason-core/managers/cargo/init.lua +++ b/lua/mason-core/managers/cargo/init.lua @@ -5,6 +5,8 @@ local a = require "mason-core.async" local Optional = require "mason-core.optional" local installer = require "mason-core.installer" local client = require "mason-core.managers.cargo.client" +local github = require "mason-core.managers.github" +local github_client = require "mason-core.managers.github.client" local _ = require "mason-core.functional" local get_bin_path = _.compose(path.concat, function(executable) @@ -23,32 +25,50 @@ local M = {} ---@async ---@param crate string The crate to install. ----@param opts {git: boolean | string, features: string?, bin: string[]? }? +---@param opts { git: { url: string, tag: boolean? }, features: string?, bin: string[]? }? function M.crate(crate, opts) return function() - M.install(crate, opts).with_receipt() + if opts and opts.git and opts.git.tag then + local ctx = installer.context() + local repo = assert(opts.git.url:match "^https://github%.com/(.+)$", "git url needs to be github.com") + local source = github.tag { repo = repo } + source.with_receipt() + ctx.requested_version = Optional.of(source.tag) + M.install(crate, opts) + else + M.install(crate, opts).with_receipt() + end end end ---@async ---@param crate string The crate to install. ----@param opts {git: boolean | string, features: string?, bin: string[]? }? +---@param opts { git: { url: string, tag: boolean? }, features: string?, bin: string[]? }? function M.install(crate, opts) local ctx = installer.context() opts = opts or {} - ctx.requested_version:if_present(function() - assert(not opts.git, "Providing a version when installing a git crate is not allowed.") - end) - ---@type string | string[] - local final_crate = crate + local version if opts.git then - final_crate = { "--git" } - if type(opts.git) == "string" then - table.insert(final_crate, opts.git) + if opts.git.tag then + assert(ctx.requested_version:is_present(), "version is required when installing tagged git crate.") end - table.insert(final_crate, crate) + version = ctx.requested_version + :map(function(version) + if opts.git.tag then + return { "--tag", version } + else + return { "--rev", version } + end + end) + :or_else(vim.NIL) + else + version = ctx.requested_version + :map(function(version) + return { "--version", version } + end) + :or_else(vim.NIL) end ctx.spawn.cargo { @@ -56,13 +76,10 @@ function M.install(crate, opts) "--root", ".", "--locked", - ctx.requested_version - :map(function(version) - return { "--version", version } - end) - :or_else(vim.NIL), + version, + opts.git and { "--git", opts.git.url } or vim.NIL, opts.features and { "--features", opts.features } or vim.NIL, - final_crate, + crate, } if opts.bin then @@ -76,42 +93,39 @@ function M.install(crate, opts) } end +---@alias InstalledCrate { name: string, version: string, github_ref: { owner: string, repo: string, ref: string }? } + +---@param line string +---@return InstalledCrate? crate +local function parse_installed_crate(line) + local name, version, context = line:match "^(.+)%s+v([^%s:]+) ?(.*):$" + if context then + local owner, repo, ref = context:match "^%(https://github%.com/(.+)/([^?]+).*#(.+)%)$" + if ref then + return { name = name, version = ref, github_ref = { owner = owner, repo = repo, ref = ref } } + end + end + if name and version then + return { name = name, version = version } + end +end + ---@param output string The `cargo install --list` output. ----@return table<string, string> # Key is the crate name, value is its version. +---@return table<string, InstalledCrate> # Key is the crate name, value is its version. function M.parse_installed_crates(output) local installed_crates = {} for _, line in ipairs(vim.split(output, "\n")) do - local name, version = line:match "^(.+)%s+v([.%S]+)[%s:]" - if name and version then - installed_crates[name] = version + local installed_crate = parse_installed_crate(line) + if installed_crate then + installed_crates[installed_crate.name] = installed_crate end end return installed_crates end ---@async ----@param receipt InstallReceipt<InstallReceiptPackageSource> ---@param install_dir string -function M.check_outdated_primary_package(receipt, install_dir) - return M.get_installed_primary_package_version(receipt, install_dir):map_catching(function(installed_version) - ---@type CrateResponse - local crate_response = client.fetch_crate(receipt.primary_source.package):get_or_throw() - if installed_version ~= crate_response.crate.max_stable_version then - return { - name = receipt.primary_source.package, - current_version = installed_version, - latest_version = crate_response.crate.max_stable_version, - } - else - error "Primary package is not outdated." - end - end) -end - ----@async ----@param receipt InstallReceipt<InstallReceiptPackageSource> ----@param install_dir string -function M.get_installed_primary_package_version(receipt, install_dir) +local function get_installed_crates(install_dir) return spawn .cargo({ "install", @@ -121,13 +135,68 @@ function M.get_installed_primary_package_version(receipt, install_dir) cwd = install_dir, }) :map_catching(function(result) - local installed_crates = M.parse_installed_crates(result.stdout) - if vim.in_fast_event() then - a.scheduler() -- needed because vim.fn.* call - end - local pkg = vim.fn.fnamemodify(receipt.primary_source.package, ":t") - return Optional.of_nilable(installed_crates[pkg]):or_else_throw "Failed to find cargo package version." + return M.parse_installed_crates(result.stdout) end) end +---@async +---@param receipt InstallReceipt<InstallReceiptPackageSource> +---@param install_dir string +function M.check_outdated_primary_package(receipt, install_dir) + if vim.in_fast_event() then + a.scheduler() + end + local crate_name = vim.fn.fnamemodify(receipt.primary_source.package, ":t") + return get_installed_crates(install_dir) + :ok() + :map(_.prop(crate_name)) + :map( + ---@param installed_crate InstalledCrate + function(installed_crate) + if installed_crate.github_ref then + ---@type GitHubCommit + local latest_commit = github_client + .fetch_commits( + ("%s/%s"):format(installed_crate.github_ref.owner, installed_crate.github_ref.repo), + { page = 1, per_page = 1 } + ) + :get_or_throw("Failed to fetch latest commits.")[1] + if not vim.startswith(latest_commit.sha, installed_crate.github_ref.ref) then + return { + name = receipt.primary_source.package, + current_version = installed_crate.github_ref.ref, + latest_version = latest_commit.sha, + } + end + else + ---@type CrateResponse + local crate_response = client.fetch_crate(crate_name):get_or_throw() + if installed_crate.version ~= crate_response.crate.max_stable_version then + return { + name = receipt.primary_source.package, + current_version = installed_crate.version, + latest_version = crate_response.crate.max_stable_version, + } + end + end + end + ) + :ok_or(_.always "Primary package is not outdated.") +end + +---@async +---@param receipt InstallReceipt<InstallReceiptPackageSource> +---@param install_dir string +function M.get_installed_primary_package_version(receipt, install_dir) + return get_installed_crates(install_dir):map(function(pkgs) + if vim.in_fast_event() then + a.scheduler() + end + local pkg = vim.fn.fnamemodify(receipt.primary_source.package, ":t") + return Optional.of_nilable(pkgs[pkg]) + :map(_.prop "version") + :or_else_throw "Failed to find cargo package version." + end) +end + return M diff --git a/lua/mason-core/managers/github/client.lua b/lua/mason-core/managers/github/client.lua index 8f4545e8..788b402b 100644 --- a/lua/mason-core/managers/github/client.lua +++ b/lua/mason-core/managers/github/client.lua @@ -8,10 +8,16 @@ local M = {} ---@alias GitHubReleaseAsset {url: string, id: integer, name: string, browser_download_url: string, created_at: string, updated_at: string, size: integer, download_count: integer} ---@alias GitHubRelease {tag_name: string, prerelease: boolean, draft: boolean, assets:GitHubReleaseAsset[]} ---@alias GitHubTag {name: string} +---@alias GitHubCommit {sha: string} ---@param path string +---@param opts { params: table<string, any>? }? ---@return Result # JSON decoded response. -local function api_call(path) +local function api_call(path, opts) + if opts and opts.params then + local params = _.join("&", _.map(_.join "=", _.sort_by(_.head, _.to_pairs(opts.params)))) + path = ("%s?%s"):format(path, params) + end return spawn .gh({ "api", path, env = { CLICOLOR_FORCE = 0 } }) :map(_.prop "stdout") @@ -21,6 +27,8 @@ local function api_call(path) :map_catching(vim.json.decode) end +M.api_call = api_call + ---@async ---@param repo string The GitHub repo ("username/repo"). ---@return Result # Result<GitHubRelease[]> @@ -105,6 +113,22 @@ function M.fetch_latest_tag(repo) :map(_.prop "tag") end +---@async +---@param repo string The GitHub repo ("username/repo"). +---@param opts { page: integer?, per_page: integer? }? +---@return Result # Result<GitHubCommit[]> +function M.fetch_commits(repo, opts) + local path = ("repos/%s/commits"):format(repo) + return api_call(path, { + params = { + page = opts and opts.page or 1, + per_page = opts and opts.per_page or 30, + }, + }):map_err(function() + return ("Failed to fetch commits for GitHub repository %s."):format(repo) + end) +end + ---@alias GitHubRateLimit {limit: integer, remaining: integer, reset: integer, used: integer} ---@alias GitHubRateLimitResponse {resources: { core: GitHubRateLimit }} |
