cargo-exe 1.0.1

A Cargo plugin to print the path to the output executable from `cargo build`.
Documentation
//! 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())
}