use color_eyre::eyre::{eyre, Result};
use url::Url;
use crate::cli::command::Command;
use crate::config::Config;
use crate::output::Output;
use crate::plugins::{unalias_plugin, ExternalPlugin, Plugin, PluginName};
use crate::tool::Tool;
use crate::toolset::ToolsetBuilder;
use crate::ui::multi_progress_report::MultiProgressReport;
#[derive(Debug, clap::Args)]
#[clap(visible_aliases = ["i", "a"], alias = "add", verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct PluginsInstall {
#[clap(required_unless_present = "all", verbatim_doc_comment)]
name: Option<String>,
#[clap(help = "The git url of the plugin", value_hint = clap::ValueHint::Url, verbatim_doc_comment)]
git_url: Option<String>,
#[clap(short, long, verbatim_doc_comment)]
force: bool,
#[clap(short, long, conflicts_with_all = ["name", "force"], verbatim_doc_comment)]
all: bool,
#[clap(long, short, action = clap::ArgAction::Count, verbatim_doc_comment)]
verbose: u8,
#[clap(hide = true)]
rest: Vec<String>,
}
impl Command for PluginsInstall {
fn run(self, mut config: Config, _out: &mut Output) -> Result<()> {
let mpr = MultiProgressReport::new(config.show_progress_bars());
if self.all {
return self.install_all_missing_plugins(config, mpr);
}
let (name, git_url) = get_name_and_url(&self.name.clone().unwrap(), &self.git_url)?;
if git_url.is_some() {
self.install_one(&mut config, name, git_url, &mpr)?;
} else {
let mut plugins: Vec<PluginName> = vec![name];
if let Some(second) = self.git_url.clone() {
plugins.push(second);
};
plugins.extend(self.rest.clone());
self.install_many(config, plugins, mpr)?;
}
Ok(())
}
}
impl PluginsInstall {
fn install_all_missing_plugins(
&self,
mut config: Config,
mpr: MultiProgressReport,
) -> Result<()> {
let ts = ToolsetBuilder::new().build(&mut config)?;
let missing_plugins = ts.list_missing_plugins(&mut config);
if missing_plugins.is_empty() {
warn!("all plugins already installed");
}
self.install_many(config, missing_plugins, mpr)?;
Ok(())
}
fn install_many(
&self,
mut config: Config,
plugins: Vec<PluginName>,
mpr: MultiProgressReport,
) -> Result<()> {
for plugin in plugins {
self.install_one(&mut config, plugin, None, &mpr)?;
}
Ok(())
}
fn install_one(
&self,
config: &mut Config,
name: PluginName,
git_url: Option<String>,
mpr: &MultiProgressReport,
) -> Result<()> {
let mut plugin = ExternalPlugin::new(name.clone());
plugin.repo_url = git_url;
if !self.force && plugin.is_installed() {
mpr.warn(format!("Plugin {} already installed", name));
mpr.warn("Use --force to install anyway".to_string());
} else {
let tool = Tool::new(plugin.name.clone(), Box::new(plugin));
tool.ensure_installed(config, Some(mpr), true)?;
}
Ok(())
}
}
fn get_name_and_url(name: &str, git_url: &Option<String>) -> Result<(String, Option<String>)> {
let name = unalias_plugin(name);
Ok(match git_url {
Some(url) => match url.contains("://") {
true => (name.to_string(), Some(url.clone())),
false => (name.to_string(), None),
},
None => match name.contains("://") {
true => (get_name_from_url(name)?, Some(name.to_string())),
false => (name.to_string(), None),
},
})
}
fn get_name_from_url(url: &str) -> Result<String> {
if let Ok(url) = Url::parse(url) {
if let Some(segments) = url.path_segments() {
let last = segments.last().unwrap_or_default();
let name = last.strip_prefix("asdf-").unwrap_or(last);
let name = name.strip_prefix("rtx-").unwrap_or(name);
let name = name.strip_suffix(".git").unwrap_or(name);
return Ok(unalias_plugin(name).to_string());
}
}
Err(eyre!("could not infer plugin name from url: {}", url))
}
static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
# install the node via shorthand
$ <bold>rtx plugins install node</bold>
# install the node plugin using a specific git url
$ <bold>rtx plugins install node https://github.com/rtx-plugins/rtx-nodejs.git</bold>
# install the node plugin using the git url only
# (node is inferred from the url)
$ <bold>rtx plugins install https://github.com/rtx-plugins/rtx-nodejs.git</bold>
# install the node plugin using a specific ref
$ <bold>rtx plugins install node https://github.com/rtx-plugins/rtx-nodejs.git#v1.0.0</bold>
"#
);
#[cfg(test)]
mod tests {
use insta::assert_display_snapshot;
use crate::cli::tests::cli_run;
#[test]
fn test_plugin_install_invalid_url() {
let args = ["rtx", "plugin", "add", "tiny:"].map(String::from).into();
let err = cli_run(&args).unwrap_err();
assert_display_snapshot!(err);
}
}