diff --git a/Cargo.lock b/Cargo.lock index d07fc345..940ce5c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2319,11 +2319,10 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.11.0", "libc", ] @@ -3321,12 +3320,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" -dependencies = [ - "num-traits", -] +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" [[package]] name = "qoi" @@ -3746,9 +3742,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.57.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fe22d10a0e39c1134a971d5b8db8a40357b48ef22d81fa8d6eac22202dd782" +checksum = "afe62631a04a1f4d71a14b99505483b95ff97c503b67d876c042fce659186956" dependencies = [ "aes", "bitflags 2.11.0", diff --git a/Cargo.toml b/Cargo.toml index e66ec0fe..0851f7f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ percent-encoding = "2.3.2" rand = { version = "0.9.2", default-features = false, features = [ "os_rng", "small_rng", "std" ] } ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] } regex = "1.12.3" -russh = { version = "0.57.0", default-features = false, features = [ "ring", "rsa" ] } +russh = { version = "0.57.1", default-features = false, features = [ "ring", "rsa" ] } scopeguard = "1.2.0" serde = { version = "1.0.228", features = [ "derive" ] } serde_json = "1.0.149" diff --git a/yazi-codegen/src/lib.rs b/yazi-codegen/src/lib.rs index d5d40ba0..47d9b99d 100644 --- a/yazi-codegen/src/lib.rs +++ b/yazi-codegen/src/lib.rs @@ -1,40 +1,50 @@ use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::{Attribute, Data, DeriveInput, Fields, parse_macro_input}; +use quote::quote; +use syn::{Data, DeriveInput, Fields, parse_macro_input}; + +#[proc_macro_derive(DeserializeOver)] +pub fn deserialize_over(input: TokenStream) -> TokenStream { + let DeriveInput { ident, .. } = parse_macro_input!(input as DeriveInput); + + quote! { + impl #ident { + pub(crate) fn deserialize_over(self, input: &str) -> Result { + crate::error_with_input(self.deserialize_over_with(toml::de::DeTable::parse(input)?), input) + } + } + } + .into() +} #[proc_macro_derive(DeserializeOver1)] pub fn deserialize_over1(input: TokenStream) -> TokenStream { - // Parse the input tokens into a syntax tree - let input = parse_macro_input!(input as DeriveInput); + let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput); - // Get the name of the struct - let name = &input.ident; - let shadow_name = format_ident!("__{name}Shadow"); - - // Process the struct fields - let (shadow_fields, field_calls) = match &input.data { - Data::Struct(struct_) => match &struct_.fields { + let assignments = match data { + Data::Struct(struct_) => match struct_.fields { Fields::Named(fields) => { - let mut shadow_fields = Vec::with_capacity(fields.named.len()); - let mut field_calls = Vec::with_capacity(fields.named.len()); + let mut assignments = Vec::with_capacity(fields.named.len()); - for field in &fields.named { - let name = &field.ident; - let attrs: Vec<&Attribute> = - field.attrs.iter().filter(|&a| a.path().is_ident("serde")).collect(); + for field in fields.named { + let field_ident = &field.ident; + let field_name = field_ident.as_ref().unwrap().to_string(); - shadow_fields.push(quote! { - #(#attrs)* - pub(crate) #name: Option - }); - field_calls.push(quote! { - if let Some(value) = shadow.#name { - self.#name = self.#name.deserialize_over(value).map_err(serde::de::Error::custom)?; + assignments.push(quote! { + if let Some(value) = table.remove(#field_name) { + if !matches!(value.get_ref(), toml::de::DeValue::Table(_)) { + _ = toml::Table::deserialize(value.into_deserializer())?; + return Err(serde::de::Error::custom(format!("expected top-level `{}` to be a TOML table", #field_name))); + } + + let span = value.span(); + if let toml::de::DeValue::Table(table) = value.into_inner() { + self.#field_ident = self.#field_ident.deserialize_over_with(toml::Spanned::new(span, table))?; + } } }); } - (shadow_fields, field_calls) + assignments } _ => panic!("DeserializeOver1 only supports structs with named fields"), }, @@ -42,34 +52,14 @@ pub fn deserialize_over1(input: TokenStream) -> TokenStream { }; quote! { - #[derive(serde::Deserialize)] - pub(crate) struct #shadow_name { - #(#shadow_fields),* - } - - impl #name { + impl #ident { #[inline] - pub(crate) fn deserialize_over<'de, D>(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - self.deserialize_over_with::(Self::deserialize_shadow(deserializer)?) - } + pub(crate) fn deserialize_over_with<'de>(mut self, table: toml::Spanned>) -> Result { + use serde::{Deserialize, de::IntoDeserializer}; - #[inline] - pub(crate) fn deserialize_shadow<'de, D>(deserializer: D) -> Result<#shadow_name, D::Error> - where - D: serde::Deserializer<'de>, - { - #shadow_name::deserialize(deserializer) - } + let mut table = table.into_inner(); + #(#assignments)* - #[inline] - pub(crate) fn deserialize_over_with<'de, D>(mut self, shadow: #shadow_name) -> Result - where - D: serde::Deserializer<'de>, - { - #(#field_calls)* Ok(self) } } @@ -79,33 +69,25 @@ pub fn deserialize_over1(input: TokenStream) -> TokenStream { #[proc_macro_derive(DeserializeOver2)] pub fn deserialize_over2(input: TokenStream) -> TokenStream { - // Parse the input tokens into a syntax tree - let input = parse_macro_input!(input as DeriveInput); + let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput); - // Get the name of the struct - let name = &input.ident; - let shadow_name = format_ident!("__{name}Shadow"); - - // Process the struct fields - let (shadow_fields, field_assignments) = match &input.data { - Data::Struct(struct_) => match &struct_.fields { + let assignments = match data { + Data::Struct(struct_) => match struct_.fields { Fields::Named(fields) => { - let mut shadow_fields = Vec::with_capacity(fields.named.len()); - let mut field_assignments = Vec::with_capacity(fields.named.len()); + let mut assignments = Vec::with_capacity(fields.named.len()); - for field in &fields.named { - let (ty, name) = (&field.ty, &field.ident); - shadow_fields.push(quote! { - pub(crate) #name: Option<#ty> - }); - field_assignments.push(quote! { - if let Some(value) = shadow.#name { - self.#name = value; + for field in fields.named { + let field_ident = field.ident; + let field_name = field_ident.as_ref().unwrap().to_string(); + + assignments.push(quote! { + if let Some(value) = table.remove(#field_name) { + self.#field_ident = <_>::deserialize(value.into_deserializer())?; } }); } - (shadow_fields, field_assignments) + assignments } _ => panic!("DeserializeOver2 only supports structs with named fields"), }, @@ -113,32 +95,15 @@ pub fn deserialize_over2(input: TokenStream) -> TokenStream { }; quote! { - #[derive(serde::Deserialize)] - pub(crate) struct #shadow_name { - #(#shadow_fields),* - } - - impl #name { + impl #ident { #[inline] - pub(crate) fn deserialize_over<'de, D>(mut self, deserializer: D) -> Result - where - D: serde::Deserializer<'de> - { - Ok(self.deserialize_over_with(Self::deserialize_shadow(deserializer)?)) - } + pub(crate) fn deserialize_over_with<'de>(mut self, table: toml::Spanned>) -> Result { + use serde::{Deserialize, de::IntoDeserializer}; - #[inline] - pub(crate) fn deserialize_shadow<'de, D>(deserializer: D) -> Result<#shadow_name, D::Error> - where - D: serde::Deserializer<'de> - { - #shadow_name::deserialize(deserializer) - } + let mut table = table.into_inner(); + #(#assignments)* - #[inline] - pub(crate) fn deserialize_over_with(mut self, shadow: #shadow_name) -> Self { - #(#field_assignments)* - self + Ok(self) } } } diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index 4d922026..a6e3b082 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -1,12 +1,12 @@ use anyhow::{Context, Result}; use serde::Deserialize; -use yazi_codegen::DeserializeOver1; +use yazi_codegen::{DeserializeOver, DeserializeOver1}; use yazi_fs::{Xdg, ok_or_not_found}; use yazi_shared::Layer; use super::{Chord, KeymapRules}; -#[derive(Deserialize, DeserializeOver1)] +#[derive(Deserialize, DeserializeOver, DeserializeOver1)] pub struct Keymap { pub mgr: KeymapRules, pub tasks: KeymapRules, diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index 023e796c..572ed851 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -25,8 +25,8 @@ fn try_init(merge: bool) -> anyhow::Result<()> { let mut keymap = Preset::keymap()?; if merge { - yazi = yazi.deserialize_over(toml::Deserializer::parse(&yazi::Yazi::read()?)?)?; - keymap = keymap.deserialize_over(toml::Deserializer::parse(&keymap::Keymap::read()?)?)?; + yazi = yazi.deserialize_over(&yazi::Yazi::read()?)?; + keymap = keymap.deserialize_over(&keymap::Keymap::read()?)?; } YAZI.init(yazi.reshape()?); @@ -43,17 +43,19 @@ pub fn init_flavor(light: bool) -> anyhow::Result<()> { } fn try_init_flavor(light: bool, merge: bool) -> anyhow::Result<()> { - let mut theme = Preset::theme(light)?; + let mut preset = Preset::theme(light)?; if merge { - let shadow = - theme::Theme::deserialize_shadow(toml::Deserializer::parse(&theme::Theme::read()?)?)?; - let flavor = shadow.flavor.as_ref().map(theme::Flavor::from).unwrap_or_default().read(light)?; - theme = theme.deserialize_over(toml::Deserializer::parse(&flavor)?)?; - theme = theme.deserialize_over_with::(shadow)?; + let theme_str = theme::Theme::read()?; + let theme = toml::de::DeTable::parse(&theme_str)?; + + let flavor_str = theme::Flavor::from_theme(&theme, &theme_str)?.read(light)?; + + preset = preset.deserialize_over(&flavor_str)?; + preset = error_with_input(preset.deserialize_over_with(theme), &theme_str)?; } - THEME.init(theme.reshape(light)?); + THEME.init(preset.reshape(light)?); Ok(()) } @@ -78,3 +80,13 @@ fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> { TTY.reader().read_exact(&mut [0])?; Ok(()) } + +pub(crate) fn error_with_input( + result: Result, + input: &str, +) -> Result { + result.map_err(|mut err| { + err.set_input(Some(input)); + err + }) +} diff --git a/yazi-config/src/opener/opener.rs b/yazi-config/src/opener/opener.rs index 1263741d..f6b5702e 100644 --- a/yazi-config/src/opener/opener.rs +++ b/yazi-config/src/opener/opener.rs @@ -3,12 +3,14 @@ use std::{mem, ops::Deref}; use anyhow::Result; use hashbrown::HashMap; use indexmap::IndexSet; -use serde::Deserialize; +use serde::{Deserialize, de::IntoDeserializer}; +use toml::{Spanned, de::DeTable}; +use yazi_codegen::DeserializeOver; use super::OpenerRule; use crate::check_for; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, DeserializeOver)] pub struct Opener(HashMap>); impl Deref for Opener { @@ -56,12 +58,13 @@ impl Opener { Ok(self) } - pub(crate) fn deserialize_over<'de, D>(mut self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let map: HashMap> = HashMap::deserialize(deserializer)?; - self.0.extend(map); + pub(crate) fn deserialize_over_with<'de>( + mut self, + table: Spanned>, + ) -> Result { + for (key, value) in table.into_inner() { + self.0.insert(key.into_inner().into_owned(), <_>::deserialize(value.into_deserializer())?); + } Ok(self) } diff --git a/yazi-config/src/theme/flavor.rs b/yazi-config/src/theme/flavor.rs index bc81c60d..c06aaaab 100644 --- a/yazi-config/src/theme/flavor.rs +++ b/yazi-config/src/theme/flavor.rs @@ -1,33 +1,30 @@ use std::path::PathBuf; use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; -use toml::Value; +use serde::{Deserialize, Serialize, de::IntoDeserializer}; +use toml::{Spanned, de::DeTable}; use yazi_codegen::DeserializeOver2; use yazi_fs::Xdg; +use crate::error_with_input; + #[derive(Default, Deserialize, DeserializeOver2, Serialize)] pub struct Flavor { + #[serde(default)] pub dark: String, + #[serde(default)] pub light: String, } -impl From<&Value> for Flavor { - fn from(value: &Value) -> Self { - let mut me = Self::default(); - if let Value::Table(t) = value { - if let Some(s) = t.get("dark").and_then(|v| v.as_str()) { - me.dark = s.to_owned(); - } - if let Some(s) = t.get("light").and_then(|v| v.as_str()) { - me.light = s.to_owned(); - } - } - me - } -} - impl Flavor { + pub(crate) fn from_theme(theme: &Spanned, input: &str) -> Result { + if let Some(value) = theme.get_ref().get("flavor").cloned() { + error_with_input(Self::deserialize(value.into_deserializer()), input) + } else { + Ok(Self::default()) + } + } + pub(crate) fn read(&self, light: bool) -> Result { Ok(match if light { self.light.as_str() } else { self.dark.as_str() } { "" => String::new(), diff --git a/yazi-config/src/theme/theme.rs b/yazi-config/src/theme/theme.rs index 516ae991..76adf672 100644 --- a/yazi-config/src/theme/theme.rs +++ b/yazi-config/src/theme/theme.rs @@ -2,13 +2,13 @@ use std::path::PathBuf; use anyhow::{Context, Result, anyhow, bail}; use serde::Deserialize; -use yazi_codegen::{DeserializeOver1, DeserializeOver2}; +use yazi_codegen::{DeserializeOver, DeserializeOver1, DeserializeOver2}; use yazi_fs::{Xdg, ok_or_not_found}; use super::{Filetype, Flavor, Icon}; use crate::{Style, normalize_path}; -#[derive(Deserialize, DeserializeOver1)] +#[derive(Deserialize, DeserializeOver, DeserializeOver1)] pub struct Theme { pub flavor: Flavor, pub app: App, diff --git a/yazi-config/src/vfs/services.rs b/yazi-config/src/vfs/services.rs index 0e8814a2..dc011dec 100644 --- a/yazi-config/src/vfs/services.rs +++ b/yazi-config/src/vfs/services.rs @@ -2,11 +2,13 @@ use std::ops::Deref; use anyhow::{Result, bail}; use hashbrown::HashMap; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::IntoDeserializer}; +use toml::{Spanned, de::DeTable}; +use yazi_codegen::DeserializeOver; use crate::vfs::Service; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, DeserializeOver)] pub struct Services(HashMap); impl Deref for Services { @@ -30,12 +32,13 @@ impl Services { Ok(self) } - pub(super) fn deserialize_over<'de, D>(mut self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let map: HashMap = HashMap::deserialize(deserializer)?; - self.0.extend(map); + pub(super) fn deserialize_over_with<'de>( + mut self, + table: Spanned>, + ) -> Result { + for (key, value) in table.into_inner() { + self.0.insert(key.into_inner().into_owned(), <_>::deserialize(value.into_deserializer())?); + } Ok(self) } diff --git a/yazi-config/src/vfs/vfs.rs b/yazi-config/src/vfs/vfs.rs index 7a405252..954a5f34 100644 --- a/yazi-config/src/vfs/vfs.rs +++ b/yazi-config/src/vfs/vfs.rs @@ -3,13 +3,13 @@ use std::io; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use tokio::sync::OnceCell; -use yazi_codegen::DeserializeOver1; +use yazi_codegen::{DeserializeOver, DeserializeOver1}; use yazi_fs::{Xdg, ok_or_not_found}; use super::Service; use crate::{Preset, vfs::Services}; -#[derive(Deserialize, Serialize, DeserializeOver1)] +#[derive(Deserialize, Serialize, DeserializeOver, DeserializeOver1)] pub struct Vfs { pub services: Services, } @@ -19,11 +19,9 @@ impl Vfs { pub static LOADED: OnceCell = OnceCell::const_new(); async fn init() -> io::Result { - tokio::task::spawn_blocking(|| { - Preset::vfs()?.deserialize_over(toml::Deserializer::parse(&Vfs::read()?)?)?.reshape() - }) - .await? - .map_err(io::Error::other) + tokio::task::spawn_blocking(|| Preset::vfs()?.deserialize_over(&Vfs::read()?)?.reshape()) + .await? + .map_err(io::Error::other) } LOADED.get_or_try_init(init).await diff --git a/yazi-config/src/yazi.rs b/yazi-config/src/yazi.rs index ba346ccf..6fb243a3 100644 --- a/yazi-config/src/yazi.rs +++ b/yazi-config/src/yazi.rs @@ -1,11 +1,11 @@ use anyhow::{Context, Result}; use serde::Deserialize; -use yazi_codegen::DeserializeOver1; +use yazi_codegen::{DeserializeOver, DeserializeOver1}; use yazi_fs::{Xdg, ok_or_not_found}; use crate::{mgr, open, opener, plugin, popup, preview, tasks, which}; -#[derive(Deserialize, DeserializeOver1)] +#[derive(Deserialize, DeserializeOver, DeserializeOver1)] pub struct Yazi { pub mgr: mgr::Mgr, pub preview: preview::Preview, diff --git a/yazi-plugin/preset/plugins/extract.lua b/yazi-plugin/preset/plugins/extract.lua index 33d6ce5b..c7f7b9cd 100644 --- a/yazi-plugin/preset/plugins/extract.lua +++ b/yazi-plugin/preset/plugins/extract.lua @@ -79,28 +79,22 @@ function M:tidy(from, to, tmp) fail("No files extracted from '%s'", from) end - local only = #outs == 1 - if only and not outs[1].cha.is_dir and require("archive").is_tar(outs[1].url) then - self:entry { args = { tostring(outs[1].url), tostring(to) } } - fs.remove("file", outs[1].url) + local only = #outs == 1 and outs[1] + if only and not only.cha.is_dir and require("archive").is_tar(only.url) then + self:entry { args = { tostring(only.url), tostring(to) } } + fs.remove("file", only.url) fs.remove("dir", tmp) return end - local target - if only then - target = to:join(outs[1].name) - else - target = to:join(self.trim_ext(from.name)) - end - - target = fs.unique(only and "file" or "dir", target) + local target = to:join(only and only.name or self.trim_ext(from.name)) + target = fs.unique(only and not only.cha.is_dir and "file" or "dir", target) if not target then fail("Failed to determine a target for '%s'", from) end - if only and not fs.rename(outs[1].url, target) then - fail('Failed to move "%s" to "%s"', outs[1].url, target) + if only and not fs.rename(only.url, target) then + fail('Failed to move "%s" to "%s"', only.url, target) elseif not only and not fs.rename(tmp, target) then fail('Failed to move "%s" to "%s"', tmp, target) end