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
//! A very simple tool to print the executable output of `cargo build`.
#![deny(missing_docs)]

use std::{path::{Path, PathBuf}, time::SystemTime};
use cargo_toml::{Error, Manifest};


/// Name of the output directory for the Debug profile.
pub const DIR_DEBUG: &str = "debug";
/// Name of the output directory for the Release profile.
pub const DIR_RELEASE: &str = "release";
/// Name of the directory which contains all output directories.
pub const DIR_TARGET: &str = "target";
/// Name of the crate manifest file.
pub const FILE_MANIFEST: &str = "Cargo.toml";


fn find_recursive(
    path_dir: &Path,
    path_sub: &Path,
    results: &mut Vec<PathBuf>,
    recurse: usize,
) -> std::io::Result<()> {
    for sub in path_dir.read_dir()? {
        if let Ok(entry) = sub {
            let path: PathBuf = entry.path();

            if path.is_dir() {
                let mut path_file: PathBuf = path.clone();
                path_file.push(path_sub);

                if path_file.is_file() {
                    results.push(path_file);
                }

                if 0 < recurse {
                    find_recursive(&path, path_sub, results, recurse - 1)?;
                }
            }
        }
    }

    Ok(())
}


/// Operational behavior. Represents the approach taken to finding an executable
///     file path.
pub enum Mode {
    /// The executable is assumed to be within `/target/debug/`.
    Debug,
    /// The executable is assumed to be within `/target/release/`.
    Release,
    /// The entirely of `/target/**/` will be searched, and the path to the most
    ///     recently modified executable will be returned.
    Latest,
}

impl Mode {
    /// Given a path to a `target/` directory and the name of an executable
    ///     file, return a path to the file according to the defined behavior.
    ///
    /// Returns `None` if the mode is `Latest` and the file cannot be found
    ///     anywhere in the target directory.
    pub fn make_path(
        &self,
        path_dir_target: impl Into<PathBuf>,
        path_exe: impl AsRef<Path>,
    ) -> Option<PathBuf> {
        let mut path: PathBuf = path_dir_target.into();
        let path_exe: &Path = path_exe.as_ref();

        match self {
            Mode::Debug => {
                path.push(DIR_DEBUG);
                path.push(path_exe);
                Some(path)
            }
            Mode::Release => {
                path.push(DIR_RELEASE);
                path.push(path_exe);
                Some(path)
            }
            Mode::Latest => {
                let mut paths = Vec::new();

                if let Err(e) = find_recursive(&path, path_exe, &mut paths, 2) {
                    eprintln!(
                        "WARNING: An error occurred while searching in `{}`. \
                        Results may not be complete.\
                        \n  Error: {}",
                        path.display(),
                        e
                    );
                }

                paths.sort_by_key(|path| {
                    path.metadata()
                        .and_then(|meta| meta.modified())
                        .unwrap_or(SystemTime::UNIX_EPOCH)
                });

                paths.pop()
            }
        }
    }
}


/// If the given Path is a file, return its parent directory. Otherwise, return
///     it unchanged.
pub fn path_project(dir_or_manifest: &Path) -> &Path {
    if dir_or_manifest.is_file() {
        match dir_or_manifest.parent() {
            Some(parent) => parent,
            None => ".".as_ref(),
        }
    } else {
        dir_or_manifest
    }
}


/// Given a path to a `Cargo.toml` crate manifest, read the manifest and attempt
///     to return the names of all executable binaries generated by the project.
///
/// Returns an `Err` if reading the manifest fails.
pub fn names_bin(path_manifest: impl AsRef<Path>) -> Result<Vec<String>, Error> {
    let manifest = Manifest::from_path(path_manifest)?;
    Ok(manifest.bin.into_iter().filter_map(|p| p.name).collect())
}