aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core/managers/cargo/init.lua
blob: 2dcc3a69c521f74d045ce67a431cc6fbfb1833e8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
local path = require "mason-core.path"
local platform = require "mason-core.platform"
local spawn = require "mason-core.spawn"
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 _ = require "mason-core.functional"

local get_bin_path = _.compose(path.concat, function(executable)
    return _.append(executable, { "bin" })
end, _.if_else(_.always(platform.is.win), _.format "%s.exe", _.identity))

---@param crate string
local function with_receipt(crate)
    return function()
        local ctx = installer.context()
        ctx.receipt:with_primary_source(ctx.receipt.cargo(crate))
    end
end

local M = {}

---@async
---@param crate string The crate to install.
---@param opts {git: boolean | string, features: string|nil, bin: string[] | nil } | nil
function M.crate(crate, opts)
    return function()
        M.install(crate, opts).with_receipt()
    end
end

---@async
---@param crate string The crate to install.
---@param opts {git: boolean | string, features: string|nil, bin: string[] | nil } | nil
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)

    local final_crate = crate

    if opts.git then
        final_crate = { "--git" }
        if type(opts.git) == "string" then
            table.insert(final_crate, opts.git)
        end
        table.insert(final_crate, crate)
    end

    ctx.spawn.cargo {
        "install",
        "--root",
        ".",
        "--locked",
        ctx.requested_version
            :map(function(version)
                return { "--version", version }
            end)
            :or_else(vim.NIL),
        opts.features and { "--features", opts.features } or vim.NIL,
        final_crate,
    }

    if opts.bin then
        _.each(function(bin)
            ctx:link_bin(bin, get_bin_path(bin))
        end, opts.bin)
    end

    return {
        with_receipt = with_receipt(crate),
    }
end

---@param output string: The `cargo install --list` output.
---@return table<string, string>: 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
        end
    end
    return installed_crates
end

---@async
---@param receipt InstallReceipt
---@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
---@param install_dir string
function M.get_installed_primary_package_version(receipt, install_dir)
    return spawn
        .cargo({
            "install",
            "--list",
            "--root",
            ".",
            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."
        end)
end

return M