cnf_lib/provider/
cwd.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//! # Current Working Directory Provider
6//!
7//! Searches for commands matching the input in the current working directory. This provider runs
8//! only in the current execution environment.
9use crate::provider::prelude::*;
10use crate::provider::CnfError;
11
12use is_executable::is_executable;
13use tokio::fs;
14
15#[derive(Debug, PartialEq)]
16pub struct Cwd {
17    path: std::path::PathBuf,
18}
19
20impl Cwd {
21    pub fn new() -> Result<Self, CnfError> {
22        Ok(Cwd {
23            path: std::env::current_dir().context("cannot create 'cwd' provider")?,
24        })
25    }
26
27    pub fn with_path(path: std::path::PathBuf) -> Self {
28        Cwd { path }
29    }
30}
31
32impl fmt::Display for Cwd {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        write!(f, "cwd")
35    }
36}
37
38#[async_trait]
39impl IsProvider for Cwd {
40    async fn search_internal(
41        &self,
42        command: &str,
43        _target_env: Arc<crate::environment::Environment>,
44    ) -> ProviderResult<Vec<Candidate>> {
45        let mut results = vec![];
46
47        if let Ok(mut diriter) = fs::read_dir(&self.path).await {
48            while let Some(entry) = diriter.next_entry().await.transpose() {
49                //for entry in diriter {
50                if entry.is_err() {
51                    continue;
52                }
53                let direntry = entry.unwrap();
54                let entrypath = &direntry.path();
55                trace!("checking file {}", entrypath.display());
56                let metadata = match fs::metadata(entrypath).await {
57                    Ok(val) => val,
58                    Err(_) => continue,
59                };
60
61                if !metadata.file_type().is_file() {
62                    trace!("skipping {}: Not a file", entrypath.display());
63                    continue;
64                }
65
66                // It's a file, check whether the name somehow matches
67                let filename = direntry.file_name();
68                let filename = match filename.to_str().to_owned() {
69                    Some(name) => name,
70                    None => {
71                        trace!("Skipping {}: Invalid unicode name", entrypath.display());
72                        continue;
73                    }
74                };
75
76                if filename.starts_with(command) {
77                    // Match!
78                    let pwd = std::env::current_dir().unwrap();
79                    let filename = pwd.join(filename).display().to_string().to_owned();
80
81                    let mut candidate = Candidate {
82                        package: filename.clone(),
83                        origin: pwd.display().to_string(),
84                        actions: Actions {
85                            install: None,
86                            execute: cmd!(filename.clone()),
87                        },
88                        ..Candidate::default()
89                    };
90
91                    if !is_executable(entrypath) {
92                        candidate.actions.install = Some(cmd!("chmod", "+x", &filename));
93                    }
94                    results.push(candidate);
95                }
96            }
97            if !results.is_empty() {
98                return Ok(results);
99            }
100        }
101        Err(ProviderError::NotFound(command.to_string()))
102    }
103}