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.
This commit is contained in:
Mika Vilpas 2024-05-09 17:57:50 +03:00
parent 928e0c6ee9
commit 58b6e75368
6 changed files with 99 additions and 76 deletions

3
Cargo.lock generated
View file

@ -2733,9 +2733,10 @@ dependencies = [
"clap_complete_nushell", "clap_complete_nushell",
"crossterm", "crossterm",
"md-5", "md-5",
"serde",
"serde_json", "serde_json",
"tokio", "tokio",
"toml_edit", "toml",
"vergen", "vergen",
"yazi-dds", "yazi-dds",
"yazi-shared", "yazi-shared",

View file

@ -17,9 +17,10 @@ anyhow = "1.0.83"
clap = { version = "4.5.4", features = [ "derive" ] } clap = { version = "4.5.4", features = [ "derive" ] }
crossterm = "0.27.0" crossterm = "0.27.0"
md-5 = "0.10.6" md-5 = "0.10.6"
serde = { version = "1.0.199", features = [ "derive" ] }
serde_json = "1.0.117" serde_json = "1.0.117"
tokio = { version = "1.37.0", features = [ "full" ] } tokio = { version = "1.37.0", features = [ "full" ] }
toml_edit = "0.22.12" toml = "0.8.12"
[build-dependencies] [build-dependencies]
anyhow = "1.0.83" anyhow = "1.0.83"

View file

@ -3,6 +3,7 @@ mod package;
use args::*; use args::*;
use clap::Parser; use clap::Parser;
use package::InstallFromConfig;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
@ -34,11 +35,11 @@ async fn main() -> anyhow::Result<()> {
Command::Pack(cmd) => { Command::Pack(cmd) => {
package::init(); package::init();
if cmd.install { if cmd.install {
package::Package::install_from_config("plugin", false).await?; package::Package::install_from_config(&InstallFromConfig::Plugin, false).await?;
package::Package::install_from_config("flavor", false).await?; package::Package::install_from_config(&InstallFromConfig::Flavor, false).await?;
} else if cmd.upgrade { } else if cmd.upgrade {
package::Package::install_from_config("plugin", true).await?; package::Package::install_from_config(&InstallFromConfig::Plugin, true).await?;
package::Package::install_from_config("flavor", true).await?; package::Package::install_from_config(&InstallFromConfig::Flavor, true).await?;
} else if let Some(repo) = &cmd.add { } else if let Some(repo) = &cmd.add {
package::Package::add_to_config(repo).await?; package::Package::add_to_config(repo).await?;
} }

View file

@ -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<GitDependency>,
}
#[derive(Serialize, Deserialize)]
pub struct FlavorConfig {
pub deps: Vec<GitDependency>,
}
#[derive(Serialize, Deserialize)]
pub struct GitDependency {
#[serde(rename = "use")]
pub use_: String,
pub commit: Option<String>,
}

View file

@ -1,6 +1,7 @@
#![allow(clippy::module_inception)] #![allow(clippy::module_inception)]
mod add; mod add;
mod config;
mod deploy; mod deploy;
mod git; mod git;
mod install; mod install;
@ -10,6 +11,7 @@ mod upgrade;
use git::*; use git::*;
pub(super) use package::*; pub(super) use package::*;
pub(super) use parser::*;
pub(super) fn init() { pub(super) fn init() {
let root = yazi_shared::Xdg::state_dir().join("packages"); let root = yazi_shared::Xdg::state_dir().join("packages");

View file

@ -1,9 +1,13 @@
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use tokio::fs; use tokio::fs;
use toml_edit::{Array, DocumentMut, InlineTable, Item, Value};
use yazi_shared::Xdg; use yazi_shared::Xdg;
use super::Package; use super::{config::{GitDependency, PackageConfig}, Package};
pub enum InstallFromConfig {
Plugin,
Flavor,
}
impl Package { impl Package {
pub(crate) async fn add_to_config(use_: &str) -> Result<()> { 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 Some(name) = package.name() else { bail!("Invalid package `use`") };
let path = Xdg::config_dir().join("package.toml"); 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?; package.add().await?;
let mut table = InlineTable::new(); let dep =
table.insert("use", package.use_().as_ref().into()); GitDependency { use_: package.use_().as_ref().into(), commit: package.commit.into() };
if !package.commit.is_empty() {
table.insert("commit", package.commit.into());
}
if package.is_flavor { if package.is_flavor {
doc["flavor"]["deps"].as_array_mut().unwrap().push(table); config.flavor.deps.push(dep);
} else { } else {
doc["plugin"]["deps"].as_array_mut().unwrap().push(table); config.plugin.deps.push(dep);
} }
fs::write(path, doc.to_string()).await?; fs::write(path, toml::to_string_pretty(&config)?).await.context("Failed to write package.toml")
Ok(())
} }
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 path = Xdg::config_dir().join("package.toml");
let Ok(s) = fs::read_to_string(&path).await else { let Ok(s) = fs::read_to_string(&path).await else {
return Ok(()); return Ok(());
}; };
let mut doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?; let mut config = Self::parse_config(&s)?;
let Some(deps) = doc.get_mut(section).and_then(|d| d.get_mut("deps")) else { let deps = match section {
return Ok(()); 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() { for dep in deps.iter_mut() {
let dep = dep.as_inline_table_mut().context("Dependency must be an inline table")?; let mut package = Package::new(&dep.use_, dep.commit.as_deref());
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);
if upgrade { if upgrade {
package.upgrade().await?; package.upgrade().await?;
} else { } else {
@ -57,57 +55,47 @@ impl Package {
} }
if package.commit.is_empty() { if package.commit.is_empty() {
dep.remove("commit"); dep.commit.take();
} else { } 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<DocumentMut> { fn parse_config(s: &str) -> Result<PackageConfig, anyhow::Error> {
let mut doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?; toml::from_str::<PackageConfig>(s).context("Failed to parse package.toml")
}
doc }
.entry("plugin")
.or_insert(toml_edit::table()) fn ensure_unique(doc: &PackageConfig, name: &str) -> Result<()> {
.as_table_mut() if doc.plugin.deps.iter().any(|v| v.use_ == name) {
.context("Failed to get `plugin` table")? bail!("Plugin `{name}` already exists in package.toml");
.entry("deps") }
.or_insert(Item::Value(Array::new().into())) if doc.flavor.deps.iter().any(|v| v.use_ == name) {
.as_array() bail!("Flavor `{name}` already exists in package.toml");
.context("Failed to get `deps` array")?; }
doc Ok(())
.entry("flavor") }
.or_insert(toml_edit::table())
.as_table_mut() #[cfg(test)]
.context("Failed to get `flavor` table")? mod tests {
.entry("deps") use super::*;
.or_insert(Item::Value(Array::new().into())) use crate::package::config::{FlavorConfig, GitDependency, PluginConfig};
.as_array()
.context("Failed to get `deps` array")?; #[test]
fn test_disallow_duplicate() {
Ok(doc) let config = PackageConfig::default();
}
assert!(ensure_unique(&config, "test").is_ok());
fn ensure_unique(doc: &DocumentMut, name: &str) -> Result<()> {
#[inline] let config = PackageConfig {
fn same(v: &Value, name: &str) -> bool { plugin: PluginConfig { deps: vec![] },
v.as_inline_table() flavor: FlavorConfig { deps: vec![GitDependency { use_: "test".into(), commit: None }] },
.and_then(|t| t.get("use")) };
.and_then(|v| v.as_str())
.is_some_and(|s| Package::new(s, None).name() == Some(name)) assert!(ensure_unique(&config, "test").is_err());
}
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(())
} }
} }