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",
"crossterm",
"md-5",
"serde",
"serde_json",
"tokio",
"toml_edit",
"toml",
"vergen",
"yazi-dds",
"yazi-shared",

View file

@ -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"

View file

@ -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?;
}

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)]
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");

View file

@ -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::<DocumentMut>().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<DocumentMut> {
let mut doc = s.parse::<DocumentMut>().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<PackageConfig, anyhow::Error> {
toml::from_str::<PackageConfig>(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());
}
}