cnf_lib/provider/
cargo.rs1use crate::provider::prelude::*;
7
8use regex::Regex;
9
10#[derive(Default, Debug, PartialEq)]
11pub struct Cargo;
12
13impl fmt::Display for Cargo {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15 write!(f, "cargo")
16 }
17}
18
19impl Cargo {
20 pub fn new() -> Self {
21 Default::default()
22 }
23
24 pub(crate) fn get_candidates_from_search_output(
25 &self,
26 output: &str,
27 ) -> ProviderResult<Vec<Candidate>> {
28 let err_context = "failed to parse output from cargo";
29 trace!(output, "parsing search output");
30
31 let lines = output
32 .lines()
33 .map(|s| s.to_string())
34 .collect::<Vec<String>>();
35
36 let mut results = vec![];
37
38 let cargo_regex = Regex::new(
39 "^(?P<package>[a-zA-Z0-9-_]+) = \"(?P<version>.*)\"\\s+# (?P<description>.+)$",
40 )
41 .context(err_context)?;
42
43 for line in lines {
44 if line.is_empty() {
45 continue;
46 }
47 match cargo_regex.captures(&line) {
48 Some(caps) => {
49 let mut candidate = Candidate {
50 package: match_to_string(&caps, 1).context(err_context)?,
51 version: match_to_string(&caps, 2).context(err_context)?,
52 description: match_to_string(&caps, 3).context(err_context)?,
53 origin: "".to_string(),
54 ..Candidate::default()
55 };
56 candidate.actions.install = Some(cmd!(
57 "cargo".into(),
58 "install".into(),
59 "--version".into(),
60 candidate.version.clone(),
61 candidate.package.clone()
62 ));
63
64 results.push(candidate);
65 }
66 None => {
67 trace!("regex didn't match on line '{}'", line);
68 continue;
69 }
70 }
71 }
72
73 Ok(results)
74 }
75}
76
77fn match_to_string(capture: ®ex::Captures, index: usize) -> ProviderResult<String> {
78 Ok(capture
79 .get(index)
80 .with_context(|| format!("failed to retrieve regex capture group {}", index))?
81 .as_str()
82 .to_string())
83}
84
85#[async_trait]
86impl IsProvider for Cargo {
87 async fn search_internal(
88 &self,
89 command: &str,
90 target_env: Arc<Environment>,
91 ) -> ProviderResult<Vec<Candidate>> {
92 let stdout = target_env
93 .output_of(cmd!(
94 "cargo", "search", "--limit", "5", "--color", "never", command
95 ))
96 .await?;
97
98 let mut candidates = self.get_candidates_from_search_output(&stdout)?;
99 for c in candidates.iter_mut() {
101 c.actions.execute = cmd!(command.to_string());
102 }
103
104 Ok(candidates)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::test::prelude::*;
112
113 #[test]
114 fn initialize() {
115 let _cargo = Cargo::new();
116 }
117
118 test::default_tests!(Cargo::new());
119
120 #[test]
125 fn matches_empty() {
126 let query = quick_test!(Cargo::new(), Ok("".to_string()));
127
128 assert::is_err!(query);
129 assert::err::not_found!(query);
130 }
131
132 #[test]
137 fn matches_zellij() {
138 let query = quick_test!(Cargo::new(), Ok("
139zellij = \"0.36.0\" # A terminal workspace with batteries included
140zellij-runner = \"0.2.0\" # Session runner/switcher for Zellij
141zellij-client = \"0.36.0\" # The client-side library for Zellij
142zellij-server = \"0.36.0\" # The server-side library for Zellij
143zellij-tile = \"0.36.0\" # A small client-side library for writing Zellij plugins
144... and 11 crates more (use --limit N to see more)
145".to_string()));
146
147 let result = query.results.unwrap();
148
149 assert!(result.len() == 5);
150 assert!(result[0].package == "zellij");
151 assert!(result[0].version == "0.36.0");
152 assert!(result[0].origin.is_empty());
153 assert!(result[0].description == "A terminal workspace with batteries included");
154 assert!(result[1].description == "Session runner/switcher for Zellij");
155 }
156
157 #[test]
162 fn no_network() {
163 let query = quick_test!(Cargo::new(), Err(ExecutionError::NonZero {
164 command: "cargo".to_string(),
165 output: std::process::Output {
166 stdout: r"".into(),
167 stderr: r"error: failed to retrieve search results from the registry at https://crates.io
168
169Caused by:
170 [6] Couldn't resolve host name (Could not resolve host: crates.io)
171".into(),
172 status: ExitStatus::from_raw(101),
173 },
174 }));
175
176 assert::is_err!(query);
177 assert::err::execution!(query);
178 }
179}