use anyhow::Context;
use clap::builder::styling::Style;
use std::{
path::{Path, PathBuf},
process::{self, Stdio},
};
pub(crate) fn find_exe(name: &str) -> Option<PathBuf> {
let file_name = format!("pint-{}", name);
all().find(|p| p.file_name().and_then(|os| os.to_str()) == Some(&file_name[..]))
}
pub(crate) fn exec(plugin_exe: &Path, args: &[String]) -> anyhow::Result<std::process::Output> {
process::Command::new(plugin_exe)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.args(args)
.output()
.with_context(|| format!("failed to execute pint plugin {plugin_exe:?}"))
}
pub(crate) fn print_all() {
let plugins: Vec<_> = all()
.map(|p| {
let file_name = p.file_name().and_then(|os| os.to_str()).unwrap_or("");
let subcommand = file_name.replace("pint-", "");
(subcommand, p)
})
.collect();
if plugins.is_empty() {
println!("No pint plugins detected in `PATH`");
return;
}
println!("Installed Plugins:");
let name_col_w = plugins
.iter()
.map(|(cmd, _)| cmd.chars().count())
.max()
.unwrap_or(0);
let bold = Style::new().bold();
for (cmd, path) in plugins {
println!(
" {}{cmd:<name_col_w$}{} {}",
bold.render(),
bold.render_reset(),
path.display()
);
}
}
fn all() -> impl Iterator<Item = PathBuf> {
let dirs = path_dirs();
dirs.into_iter()
.flat_map(walkdir::WalkDir::new)
.filter_map(Result::ok)
.map(|entry| entry.path().to_path_buf())
.filter(|p| is_plugin(p))
}
fn path_dirs() -> Vec<PathBuf> {
match std::env::var_os("PATH") {
Some(s) => std::env::split_paths(&s).collect(),
None => vec![],
}
}
fn is_plugin(path: &Path) -> bool {
if let Some(name) = path.file_name().and_then(|os| os.to_str()) {
if name.starts_with("pint-") && is_executable(path) {
return true;
}
}
false
}
fn is_executable(path: &Path) -> bool {
#[cfg(unix)]
{
use std::os::unix::prelude::*;
std::fs::metadata(path)
.map(|meta| meta.is_file() && meta.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(not(unix))]
{
path.is_file()
}
}