cnf_lib/provider/
path.rs

1// Copyright (C) 2023 Andreas Hartmann <hartan@7x.de>
2// GNU General Public License v3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5//! # Path provider.
6//!
7//! Searches for an executable in the `$PATH`. Since this provider runs on all environments, it is
8//! useful to e.g. discover executables available in a toolbx container.
9use crate::provider::prelude::*;
10
11#[derive(Debug, Default, PartialEq)]
12pub struct Path {}
13
14impl fmt::Display for Path {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(f, "$PATH")
17    }
18}
19
20impl Path {
21    pub fn new() -> Path {
22        Default::default()
23    }
24}
25
26#[async_trait]
27impl IsProvider for Path {
28    async fn search_internal(
29        &self,
30        command: &str,
31        target_env: Arc<Environment>,
32    ) -> ProviderResult<Vec<Candidate>> {
33        let result_success = |package: String| {
34            Ok(vec![Candidate {
35                package: package.clone(),
36                actions: Actions {
37                    install: None,
38                    execute: cmd!(package),
39                },
40                ..Candidate::default()
41            }])
42        };
43
44        if &crate::environment::current() == target_env.as_ref() {
45            match which::which(command) {
46                Ok(path) => result_success(path.display().to_string()),
47                Err(_) => Err(ProviderError::NotFound(command.to_string())),
48            }
49        } else {
50            let stdout = target_env
51                .output_of(cmd!("command", "-v", command))
52                .await
53                .map_err(|e| {
54                    if let ExecutionError::NonZero { .. } = e {
55                        ProviderError::NotFound(command.to_string())
56                    } else {
57                        ProviderError::from(e)
58                    }
59                })?;
60
61            result_success(stdout.trim_end().to_string())
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::test::prelude::*;
70
71    test::default_tests!(Path::new());
72
73    #[test]
74    fn search_nonexistent() {
75        let query = quick_test!(
76            Path::new(),
77            Err(ExecutionError::NonZero {
78                command: "command".to_string(),
79                output: std::process::Output {
80                    stdout: "".into(),
81                    stderr: "".into(),
82                    status: ExitStatus::from_raw(1),
83                }
84            })
85        );
86
87        assert::is_err!(query);
88        assert::err::not_found!(query);
89    }
90
91    #[test]
92    fn search_existent() {
93        let query = quick_test!(Path::new(), Ok("/usr/bin/top".to_string()));
94
95        let result = query.results.unwrap();
96
97        assert_eq!(result.len(), 1);
98        assert_eq!(result[0].package, "/usr/bin/top".to_string());
99        assert_eq!(result[0].actions.execute, vec!["/usr/bin/top"].into());
100    }
101}