use std::{path::PathBuf, str::FromStr}; use anyhow::{Context, Result, bail}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use yazi_fs::{Xdg, provider::{Provider, local::Local}}; use yazi_macro::{ok_or_not_found, outln}; use super::Dependency; #[derive(Default)] pub(crate) struct Package { pub(crate) plugins: Vec, pub(crate) flavors: Vec, } impl Package { pub(crate) async fn load() -> Result { let s = ok_or_not_found!(Local::regular(&Self::toml()).read_to_string().await); Ok(toml::from_str(&s)?) } pub(crate) async fn add_many(&mut self, uses: &[String]) -> Result<()> { for u in uses { let r = self.add(u).await; self.save().await?; r?; } Ok(()) } pub(crate) async fn delete_many(&mut self, uses: &[String], discard: bool) -> Result<()> { for u in uses { let r = self.delete(u, discard).await; self.save().await?; r?; } Ok(()) } pub(crate) async fn install(&mut self, discard: bool) -> Result<()> { macro_rules! go { ($dep:expr) => { let r = $dep.install(discard).await; self.save().await?; r?; }; } for i in 0..self.plugins.len() { go!(self.plugins[i]); } for i in 0..self.flavors.len() { go!(self.flavors[i]); } Ok(()) } pub(crate) async fn upgrade_many(&mut self, uses: &[String], discard: bool) -> Result<()> { macro_rules! go { ($dep:expr) => { if uses.is_empty() || uses.contains(&$dep.r#use) { let r = $dep.upgrade(discard).await; self.save().await?; r?; } }; } for i in 0..self.plugins.len() { go!(self.plugins[i]); } for i in 0..self.flavors.len() { go!(self.flavors[i]); } Ok(()) } pub(crate) fn print(&self) -> Result<()> { outln!("Plugins:")?; for d in &self.plugins { if d.rev.is_empty() { outln!("\t{}", d.r#use)?; } else { outln!("\t{} ({})", d.r#use, d.rev)?; } } outln!("Flavors:")?; for d in &self.flavors { if d.rev.is_empty() { outln!("\t{}", d.r#use)?; } else { outln!("\t{} ({})", d.r#use, d.rev)?; } } Ok(()) } async fn add(&mut self, r#use: &str) -> Result<()> { let mut dep = Dependency::from_str(r#use)?; if let Some(d) = self.identical(&dep) { bail!( "{} `{}` already exists in package.toml", if d.is_flavor { "Flavor" } else { "Plugin" }, dep.name ) } dep.add(false).await?; if dep.is_flavor { self.flavors.push(dep); } else { self.plugins.push(dep); } Ok(()) } async fn delete(&mut self, r#use: &str, discard: bool) -> Result<()> { let Some(dep) = self.identical(&Dependency::from_str(r#use)?).cloned() else { bail!("`{}` was not found in package.toml", r#use) }; dep.delete(discard).await?; if dep.is_flavor { self.flavors.retain(|d| !d.identical(&dep)); } else { self.plugins.retain(|d| !d.identical(&dep)); } Ok(()) } async fn save(&self) -> Result<()> { let s = toml::to_string_pretty(self)?; Local::regular(&Self::toml()).write(s).await.context("Failed to write package.toml") } fn toml() -> PathBuf { Xdg::config_dir().join("package.toml") } fn identical(&self, other: &Dependency) -> Option<&Dependency> { self.plugins.iter().chain(&self.flavors).find(|d| d.identical(other)) } } impl<'de> Deserialize<'de> for Package { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] struct Outer { #[serde(default)] plugin: Shadow, #[serde(default)] flavor: Shadow, } #[derive(Default, Deserialize)] struct Shadow { deps: Vec, } let mut outer = Outer::deserialize(deserializer)?; outer.flavor.deps.iter_mut().for_each(|d| d.is_flavor = true); Ok(Self { plugins: outer.plugin.deps, flavors: outer.flavor.deps }) } } impl Serialize for Package { fn serialize(&self, serializer: S) -> Result where S: Serializer, { #[derive(Serialize)] struct Outer<'a> { plugin: Shadow<'a>, flavor: Shadow<'a>, } #[derive(Serialize)] struct Shadow<'a> { deps: &'a [Dependency], } Outer { plugin: Shadow { deps: &self.plugins }, flavor: Shadow { deps: &self.flavors } } .serialize(serializer) } }