use crate::cli::PluginsCommand;
use anyhow::anyhow;
use clap::Parser;
use forc_tracing::println_warning;
use forc_util::ForcResult;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use tracing::info;
forc_util::cli_examples! {
crate::cli::Opt {
[ List all plugins => "forc plugins" ]
[ List all plugins with their paths => "forc plugins --paths" ]
[ List all plugins with their descriptions => "forc plugins --describe" ]
[ List all plugins with their paths and descriptions => "forc plugins --paths --describe" ]
}
}
#[derive(Debug, Parser)]
#[clap(name = "forc plugins", about = "List all forc plugins", version, after_help = help())]
pub struct Command {
#[clap(long = "paths", short = 'p')]
print_full_path: bool,
#[clap(long = "describe", short = 'd')]
describe: bool,
}
fn get_file_name(path: &Path) -> String {
if let Some(path_str) = path.file_name().and_then(|path_str| path_str.to_str()) {
path_str.to_owned()
} else {
path.display().to_string()
}
}
pub(crate) fn exec(command: PluginsCommand) -> ForcResult<()> {
let PluginsCommand {
print_full_path,
describe,
} = command;
let mut plugins = crate::cli::plugin::find_all()
.map(|path| {
get_plugin_info(path.clone(), print_full_path, describe).map(|info| (path, info))
})
.collect::<Result<Vec<(_, _)>, _>>()?
.into_iter()
.fold(HashMap::new(), |mut acc, (path, content)| {
let bin_name = get_file_name(&path);
acc.entry(bin_name.clone())
.or_insert_with(|| (bin_name, vec![], content.clone()))
.1
.push(path);
acc
})
.into_values()
.map(|(bin_name, mut paths, content)| {
paths.sort();
paths.dedup();
(bin_name, paths, content)
})
.collect::<Vec<_>>();
plugins.sort_by(|a, b| a.0.cmp(&b.0));
info!("Installed Plugins:");
for plugin in plugins {
info!("{}", plugin.2);
if plugin.1.len() > 1 {
println_warning(&format!("Multiple paths found for {}", plugin.0));
for path in plugin.1 {
println_warning(&format!(" {}", path.display()));
}
}
}
Ok(())
}
fn parse_description_for_plugin(plugin: &Path) -> String {
use std::process::Command;
let default_description = "No description found for this plugin.";
let proc = Command::new(plugin)
.arg("-h")
.output()
.expect("Could not get plugin description.");
let stdout = String::from_utf8_lossy(&proc.stdout);
match stdout.split('\n').nth(1) {
Some(x) => {
if x.is_empty() {
default_description.to_owned()
} else {
x.to_owned()
}
}
None => default_description.to_owned(),
}
}
fn format_print_description(
path: PathBuf,
print_full_path: bool,
describe: bool,
) -> ForcResult<String> {
let display = if print_full_path {
path.display().to_string()
} else {
get_file_name(&path)
};
let description = parse_description_for_plugin(&path);
if describe {
Ok(format!(" {display} \t\t{description}"))
} else {
Ok(display)
}
}
fn get_plugin_info(path: PathBuf, print_full_path: bool, describe: bool) -> ForcResult<String> {
format_print_description(path, print_full_path, describe)
.map_err(|e| anyhow!("Could not get plugin info: {}", e.as_ref()).into())
}