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
// Copyright (C) 2023 Andreas Hartmann <hartan@7x.de>
// GNU General Public License v3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
// SPDX-License-Identifier: GPL-3.0-or-later

//! # Current Working Directory Provider
//!
//! Searches for commands matching the input in the current working directory. This provider runs
//! only in the current execution environment.
use crate::provider::prelude::*;
use crate::provider::CnfError;

use async_std::{fs, prelude::*};
use is_executable::is_executable;

#[derive(Debug, PartialEq)]
pub struct Cwd {
    path: std::path::PathBuf,
}

impl Cwd {
    pub fn new() -> Result<Self, CnfError> {
        Ok(Cwd {
            path: std::env::current_dir().context("cannot create 'cwd' provider")?,
        })
    }

    pub fn with_path(path: std::path::PathBuf) -> Self {
        Cwd { path }
    }
}

impl fmt::Display for Cwd {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "cwd")
    }
}

#[async_trait]
impl IsProvider for Cwd {
    async fn search_internal(
        &self,
        command: &str,
        _target_env: Arc<crate::environment::Environment>,
    ) -> ProviderResult<Vec<Candidate>> {
        let mut results = vec![];

        if let Ok(mut diriter) = fs::read_dir(&self.path).await {
            while let Some(entry) = diriter.next().await {
                //for entry in diriter {
                if entry.is_err() {
                    continue;
                }
                let direntry = entry.unwrap();
                let entrypath = &direntry.path();
                log::debug!("Checking file {}", entrypath.display());
                let metadata = match fs::metadata(entrypath).await {
                    Ok(val) => val,
                    Err(_) => continue,
                };

                if !metadata.file_type().is_file() {
                    log::debug!("Skipping {}: Not a file", entrypath.display());
                    continue;
                }

                // It's a file, check whether the name somehow matches
                let filename = direntry.file_name();
                let filename = match filename.to_str().to_owned() {
                    Some(name) => name,
                    None => {
                        log::debug!("Skipping {}: Invalid unicode name", entrypath.display());
                        continue;
                    }
                };

                if filename.starts_with(command) {
                    // Match!
                    let pwd = std::env::current_dir().unwrap();
                    let filename = pwd.join(filename).display().to_string().to_owned();

                    let mut candidate = Candidate {
                        package: filename.clone(),
                        origin: pwd.display().to_string(),
                        actions: Actions {
                            install: None,
                            execute: cmd!(filename.clone()),
                        },
                        ..Candidate::default()
                    };

                    if !is_executable(entrypath) {
                        candidate.actions.install = Some(cmd!("chmod", "+x", &filename));
                    }
                    results.push(candidate);
                }
            }
            if !results.is_empty() {
                return Ok(results);
            }
        }
        Err(ProviderError::NotFound(command.to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::environment::current;
    use crate::provider::search_in;
    use crate::test::prelude::*;
    use async_std::task;

    #[test]
    fn initialize() {
        let _cwd = Cwd::new().unwrap();
    }

    #[test]
    fn does_not_exist() {
        let cwd = Cwd::new().unwrap();
        let query = task::block_on(search_in(
            Arc::new(cwd.into()),
            "thisfiledoesnotexist",
            Arc::new(current()),
        ));

        assert::is_err!(query);
        assert::err::not_found!(query);
    }
}