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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
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 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)
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: { url: string, tag: boolean? }, features: string?, bin: string[]? }?
function M.crate(crate, opts)
return function()
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: { url: string, tag: boolean? }, features: string?, bin: string[]? }?
function M.install(crate, opts)
local ctx = installer.context()
opts = opts or {}
local version
if opts.git then
if opts.git.tag then
assert(ctx.requested_version:is_present(), "version is required when installing tagged git crate.")
end
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 {
"install",
"--root",
".",
"--locked",
version,
opts.git and { "--git", opts.git.url } or vim.NIL,
opts.features and { "--features", opts.features } or vim.NIL,
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
---@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, 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 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 install_dir string
---@return Result # Result<table<string, InstalledCrate>>
local function get_installed_crates(install_dir)
return spawn
.cargo({
"install",
"--list",
"--root",
".",
cwd = install_dir,
})
:map_catching(function(result)
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)
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(_.prop "version")
:ok_or(_.always "Failed to find cargo package version.")
end
return M
|