use std::path::{Path, PathBuf};
use anyhow::{Context as _, Result};
use crate::app;
use crate::cli::Cli;
use crate::config::ProjectConfig;
use crate::error::{PmpxError, EXIT_OK};
use crate::style;
pub(super) fn plugin_set(args: &Cli, name: &str) -> crate::error::Result<u8> {
let session = app::session(args)?;
let Some(plugin) = session.plugins.by_name(name) else {
return Err(PmpxError::not_found(format!(
"no plugin named {name}. Installed: {}",
session
.plugins
.usable()
.map(|p| p.name())
.collect::<Vec<_>>()
.join(", ")
)));
};
let Some(family) = plugin.family.clone() else {
return Err(PmpxError::Usage(format!(
"{name}'s manifest declares no family, so it cannot be pinned."
)));
};
let path = target_config_path(&session);
let current = ProjectConfig::load_from(&path)
.map_err(PmpxError::Other)?
.unwrap_or_default();
edit_project_config(&path, current, |cfg| {
cfg.plugin
.insert(family.as_str().to_string(), plugin.name().to_string());
})
.map_err(PmpxError::Other)?;
anstream::println!(
"Pinned {} = \"{}\" in {}",
family.as_str(),
style::paint(style::PM, &plugin.name()),
style::paint(style::DIM, path.display())
);
Ok(EXIT_OK)
}
pub(super) fn plugin_unset(
args: &Cli,
family: Option<&str>,
yes: bool,
) -> crate::error::Result<u8> {
let session = app::session(args)?;
let path = target_config_path(&session);
let existing = ProjectConfig::load_from(&path)
.map_err(PmpxError::Other)?
.unwrap_or_default();
match family {
Some(f) => {
if !existing.plugin.contains_key(f) {
return Err(PmpxError::Usage(format!(
"{} has no pin for {f}",
path.display()
)));
}
edit_project_config(&path, existing, |cfg| {
cfg.plugin.remove(f);
})
.map_err(PmpxError::Other)?;
anstream::println!(
"Removed the pin for {f} from {}",
style::paint(style::DIM, path.display())
);
}
None => {
if existing.plugin.is_empty() {
return Err(PmpxError::Usage(format!(
"{} has no pins at all",
path.display()
)));
}
if existing.plugin.len() > 1 && !yes {
let mut msg = format!(
"{} has {} pins; deleting all of them needs `--yes`:",
path.display(),
existing.plugin.len()
);
for (f, p) in &existing.plugin {
msg.push_str(&format!("\n {f} = \"{p}\""));
}
return Err(PmpxError::Usage(msg));
}
let removed: Vec<String> = existing.plugin.keys().cloned().collect();
edit_project_config(&path, existing, |cfg| {
cfg.plugin.clear();
})
.map_err(PmpxError::Other)?;
anstream::println!(
"Removed the pins for {} from {}",
removed.join(", "),
style::paint(style::DIM, path.display())
);
}
}
Ok(EXIT_OK)
}
fn target_config_path(session: &pmpx_engine::Session) -> PathBuf {
if let Some(nearest) = session.project.sources.first() {
return nearest.clone();
}
let base = session
.project_root()
.map(PathBuf::from)
.unwrap_or_else(|| session.start_dir.clone());
base.join(".pmpx.toml")
}
fn edit_project_config(
path: &Path,
current: ProjectConfig,
edit: impl FnOnce(&mut ProjectConfig),
) -> Result<()> {
let mut cfg = current;
edit(&mut cfg);
if cfg.plugin.is_empty() && cfg.scripts.is_empty() && cfg.extra.is_empty() {
if path.is_file() {
std::fs::remove_file(path).with_context(|| {
format!("failed to delete the empty config file: {}", path.display())
})?;
}
return Ok(());
}
let text = toml::to_string_pretty(&cfg).context("failed to serialise the project config")?;
crate::config::atomic_write(path, &text)
}