From 58b6e75368d59de9e5adacbfd6ecf32fba30249c Mon Sep 17 00:00:00 2001 From: Mika Vilpas Date: Thu, 9 May 2024 17:57:50 +0300 Subject: [PATCH] feat(plugin): plugin config is pretty printed when saving it A pretty printed configuration file makes it easier to see the differences between two versions of the configuration file. --- Cargo.lock | 3 +- yazi-cli/Cargo.toml | 3 +- yazi-cli/src/main.rs | 9 +-- yazi-cli/src/package/config.rs | 30 ++++++++ yazi-cli/src/package/mod.rs | 2 + yazi-cli/src/package/parser.rs | 128 +++++++++++++++------------------ 6 files changed, 99 insertions(+), 76 deletions(-) create mode 100644 yazi-cli/src/package/config.rs diff --git a/Cargo.lock b/Cargo.lock index 8b95c82c..26ee4ceb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2733,9 +2733,10 @@ dependencies = [ "clap_complete_nushell", "crossterm", "md-5", + "serde", "serde_json", "tokio", - "toml_edit", + "toml", "vergen", "yazi-dds", "yazi-shared", diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml index 93d7cdf7..f850ab1e 100644 --- a/yazi-cli/Cargo.toml +++ b/yazi-cli/Cargo.toml @@ -17,9 +17,10 @@ anyhow = "1.0.83" clap = { version = "4.5.4", features = [ "derive" ] } crossterm = "0.27.0" md-5 = "0.10.6" +serde = { version = "1.0.199", features = [ "derive" ] } serde_json = "1.0.117" tokio = { version = "1.37.0", features = [ "full" ] } -toml_edit = "0.22.12" +toml = "0.8.12" [build-dependencies] anyhow = "1.0.83" diff --git a/yazi-cli/src/main.rs b/yazi-cli/src/main.rs index 98ea094b..8d45695a 100644 --- a/yazi-cli/src/main.rs +++ b/yazi-cli/src/main.rs @@ -3,6 +3,7 @@ mod package; use args::*; use clap::Parser; +use package::InstallFromConfig; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -34,11 +35,11 @@ async fn main() -> anyhow::Result<()> { Command::Pack(cmd) => { package::init(); if cmd.install { - package::Package::install_from_config("plugin", false).await?; - package::Package::install_from_config("flavor", false).await?; + package::Package::install_from_config(&InstallFromConfig::Plugin, false).await?; + package::Package::install_from_config(&InstallFromConfig::Flavor, false).await?; } else if cmd.upgrade { - package::Package::install_from_config("plugin", true).await?; - package::Package::install_from_config("flavor", true).await?; + package::Package::install_from_config(&InstallFromConfig::Plugin, true).await?; + package::Package::install_from_config(&InstallFromConfig::Flavor, true).await?; } else if let Some(repo) = &cmd.add { package::Package::add_to_config(repo).await?; } diff --git a/yazi-cli/src/package/config.rs b/yazi-cli/src/package/config.rs new file mode 100644 index 00000000..7f5a2582 --- /dev/null +++ b/yazi-cli/src/package/config.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct PackageConfig { + pub plugin: PluginConfig, + pub flavor: FlavorConfig, +} + +impl Default for PackageConfig { + fn default() -> Self { + Self { plugin: PluginConfig { deps: vec![] }, flavor: FlavorConfig { deps: vec![] } } + } +} + +#[derive(Serialize, Deserialize)] +pub struct PluginConfig { + pub deps: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct FlavorConfig { + pub deps: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct GitDependency { + #[serde(rename = "use")] + pub use_: String, + pub commit: Option, +} diff --git a/yazi-cli/src/package/mod.rs b/yazi-cli/src/package/mod.rs index 2e7ec2a1..73b2f805 100644 --- a/yazi-cli/src/package/mod.rs +++ b/yazi-cli/src/package/mod.rs @@ -1,6 +1,7 @@ #![allow(clippy::module_inception)] mod add; +mod config; mod deploy; mod git; mod install; @@ -10,6 +11,7 @@ mod upgrade; use git::*; pub(super) use package::*; +pub(super) use parser::*; pub(super) fn init() { let root = yazi_shared::Xdg::state_dir().join("packages"); diff --git a/yazi-cli/src/package/parser.rs b/yazi-cli/src/package/parser.rs index 28c63981..742a61c2 100644 --- a/yazi-cli/src/package/parser.rs +++ b/yazi-cli/src/package/parser.rs @@ -1,9 +1,13 @@ use anyhow::{bail, Context, Result}; use tokio::fs; -use toml_edit::{Array, DocumentMut, InlineTable, Item, Value}; use yazi_shared::Xdg; -use super::Package; +use super::{config::{GitDependency, PackageConfig}, Package}; + +pub enum InstallFromConfig { + Plugin, + Flavor, +} impl Package { pub(crate) async fn add_to_config(use_: &str) -> Result<()> { @@ -11,45 +15,39 @@ impl Package { let Some(name) = package.name() else { bail!("Invalid package `use`") }; let path = Xdg::config_dir().join("package.toml"); - let mut doc = Self::ensure_config(&fs::read_to_string(&path).await.unwrap_or_default())?; + let mut config = Self::parse_config(&fs::read_to_string(&path).await.unwrap_or_default())?; - Self::ensure_unique(&doc, name)?; + ensure_unique(&config, name)?; package.add().await?; - let mut table = InlineTable::new(); - table.insert("use", package.use_().as_ref().into()); - if !package.commit.is_empty() { - table.insert("commit", package.commit.into()); - } - + let dep = + GitDependency { use_: package.use_().as_ref().into(), commit: package.commit.into() }; if package.is_flavor { - doc["flavor"]["deps"].as_array_mut().unwrap().push(table); + config.flavor.deps.push(dep); } else { - doc["plugin"]["deps"].as_array_mut().unwrap().push(table); + config.plugin.deps.push(dep); } - fs::write(path, doc.to_string()).await?; - Ok(()) + fs::write(path, toml::to_string_pretty(&config)?).await.context("Failed to write package.toml") } - pub(crate) async fn install_from_config(section: &str, upgrade: bool) -> Result<()> { + pub(crate) async fn install_from_config( + section: &InstallFromConfig, + upgrade: bool, + ) -> Result<()> { let path = Xdg::config_dir().join("package.toml"); let Ok(s) = fs::read_to_string(&path).await else { return Ok(()); }; - let mut doc = s.parse::().context("Failed to parse package.toml")?; - let Some(deps) = doc.get_mut(section).and_then(|d| d.get_mut("deps")) else { - return Ok(()); + let mut config = Self::parse_config(&s)?; + let deps = match section { + InstallFromConfig::Plugin => &mut config.plugin.deps, + InstallFromConfig::Flavor => &mut config.flavor.deps, }; - let deps = deps.as_array_mut().context("`deps` must be an array")?; for dep in deps.iter_mut() { - let dep = dep.as_inline_table_mut().context("Dependency must be an inline table")?; - let use_ = dep.get("use").and_then(|d| d.as_str()).context("Missing `use` field")?; - let commit = dep.get("commit").and_then(|d| d.as_str()); - - let mut package = Package::new(use_, commit); + let mut package = Package::new(&dep.use_, dep.commit.as_deref()); if upgrade { package.upgrade().await?; } else { @@ -57,57 +55,47 @@ impl Package { } if package.commit.is_empty() { - dep.remove("commit"); + dep.commit.take(); } else { - dep.insert("commit", package.commit.into()); + dep.commit = Some(package.commit); } } - fs::write(path, doc.to_string()).await.context("Failed to write package.toml") + fs::write(path, toml::to_string_pretty(&config)?).await.context("Failed to write package.toml") } - fn ensure_config(s: &str) -> Result { - let mut doc = s.parse::().context("Failed to parse package.toml")?; - - doc - .entry("plugin") - .or_insert(toml_edit::table()) - .as_table_mut() - .context("Failed to get `plugin` table")? - .entry("deps") - .or_insert(Item::Value(Array::new().into())) - .as_array() - .context("Failed to get `deps` array")?; - - doc - .entry("flavor") - .or_insert(toml_edit::table()) - .as_table_mut() - .context("Failed to get `flavor` table")? - .entry("deps") - .or_insert(Item::Value(Array::new().into())) - .as_array() - .context("Failed to get `deps` array")?; - - Ok(doc) - } - - fn ensure_unique(doc: &DocumentMut, name: &str) -> Result<()> { - #[inline] - fn same(v: &Value, name: &str) -> bool { - v.as_inline_table() - .and_then(|t| t.get("use")) - .and_then(|v| v.as_str()) - .is_some_and(|s| Package::new(s, None).name() == Some(name)) - } - - if doc["plugin"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) { - bail!("Plugin `{name}` already exists in package.toml"); - } - if doc["flavor"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) { - bail!("Flavor `{name}` already exists in package.toml"); - } - - Ok(()) + fn parse_config(s: &str) -> Result { + toml::from_str::(s).context("Failed to parse package.toml") + } +} + +fn ensure_unique(doc: &PackageConfig, name: &str) -> Result<()> { + if doc.plugin.deps.iter().any(|v| v.use_ == name) { + bail!("Plugin `{name}` already exists in package.toml"); + } + if doc.flavor.deps.iter().any(|v| v.use_ == name) { + bail!("Flavor `{name}` already exists in package.toml"); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::package::config::{FlavorConfig, GitDependency, PluginConfig}; + + #[test] + fn test_disallow_duplicate() { + let config = PackageConfig::default(); + + assert!(ensure_unique(&config, "test").is_ok()); + + let config = PackageConfig { + plugin: PluginConfig { deps: vec![] }, + flavor: FlavorConfig { deps: vec![GitDependency { use_: "test".into(), commit: None }] }, + }; + + assert!(ensure_unique(&config, "test").is_err()); } }