mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: keep span info when config parsing fails
This commit is contained in:
parent
3cdc3ecb70
commit
dfeafa995c
10 changed files with 132 additions and 162 deletions
|
|
@ -1,40 +1,47 @@
|
|||
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<Self, toml::de::Error> {
|
||||
crate::error_with_input(self.deserialize_over_with(crate::parse_recoverable(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<toml::Value>
|
||||
});
|
||||
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) {
|
||||
let span = value.span();
|
||||
let table = match value.into_inner() {
|
||||
toml::de::DeValue::Table(table) => table,
|
||||
_ => return Err(serde::de::Error::custom(format!("expected top-level `{}` to be a TOML table", #field_name))),
|
||||
};
|
||||
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 +49,11 @@ 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<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
self.deserialize_over_with::<D>(Self::deserialize_shadow(deserializer)?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn deserialize_shadow<'de, D>(deserializer: D) -> Result<#shadow_name, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#shadow_name::deserialize(deserializer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn deserialize_over_with<'de, D>(mut self, shadow: #shadow_name) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#(#field_calls)*
|
||||
pub(crate) fn deserialize_over_with<'de>(mut self, table: toml::Spanned<toml::de::DeTable<'de>>) -> Result<Self, toml::de::Error> {
|
||||
let mut table = table.into_inner();
|
||||
#(#assignments)*
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -79,33 +63,26 @@ 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 (ty, field_ident) = (field.ty, field.ident);
|
||||
let field_name = field_ident.as_ref().unwrap().to_string();
|
||||
|
||||
assignments.push(quote! {
|
||||
if let Some(value) = table.remove(#field_name) {
|
||||
let de = serde::de::IntoDeserializer::into_deserializer(value);
|
||||
self.#field_ident = <#ty as serde::Deserialize>::deserialize(de)?;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
(shadow_fields, field_assignments)
|
||||
assignments
|
||||
}
|
||||
_ => panic!("DeserializeOver2 only supports structs with named fields"),
|
||||
},
|
||||
|
|
@ -113,32 +90,12 @@ 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<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>
|
||||
{
|
||||
Ok(self.deserialize_over_with(Self::deserialize_shadow(deserializer)?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn deserialize_shadow<'de, D>(deserializer: D) -> Result<#shadow_name, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>
|
||||
{
|
||||
#shadow_name::deserialize(deserializer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn deserialize_over_with(mut self, shadow: #shadow_name) -> Self {
|
||||
#(#field_assignments)*
|
||||
self
|
||||
pub(crate) fn deserialize_over_with<'de>(mut self, table: toml::Spanned<toml::de::DeTable<'de>>) -> Result<Self, toml::de::Error> {
|
||||
let mut table = table.into_inner();
|
||||
#(#assignments)*
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ yazi_macro::mod_flat!(icon layout pattern platform preset priority style utils y
|
|||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use toml::{Spanned, de::DeTable};
|
||||
use yazi_shared::{RoCell, SyncCell};
|
||||
use yazi_tty::TTY;
|
||||
|
||||
|
|
@ -25,8 +26,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 +44,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::<toml::Value>(shadow)?;
|
||||
let theme_str = theme::Theme::read()?;
|
||||
let theme = parse_recoverable(&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 +81,20 @@ fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> {
|
|||
TTY.reader().read_exact(&mut [0])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn parse_recoverable<'a>(
|
||||
input: &'a str,
|
||||
) -> Result<Spanned<DeTable<'a>>, toml::de::Error> {
|
||||
let (table, errors) = DeTable::parse_recoverable(input);
|
||||
if let Some(err) = errors.into_iter().next() { Err(err) } else { Ok(table) }
|
||||
}
|
||||
|
||||
pub(crate) fn error_with_input<T>(
|
||||
result: Result<T, toml::de::Error>,
|
||||
input: &str,
|
||||
) -> Result<T, toml::de::Error> {
|
||||
result.map_err(|mut err| {
|
||||
err.set_input(Some(input));
|
||||
err
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Vec<OpenerRule>>);
|
||||
|
||||
impl Deref for Opener {
|
||||
|
|
@ -56,12 +58,13 @@ impl Opener {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_over<'de, D>(mut self, deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let map: HashMap<String, Vec<OpenerRule>> = HashMap::deserialize(deserializer)?;
|
||||
self.0.extend(map);
|
||||
pub(crate) fn deserialize_over_with<'de>(
|
||||
mut self,
|
||||
table: Spanned<DeTable<'de>>,
|
||||
) -> Result<Self, toml::de::Error> {
|
||||
for (key, value) in table.into_inner() {
|
||||
self.0.insert(key.into_inner().into_owned(), <_>::deserialize(value.into_deserializer())?);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,28 @@
|
|||
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 {
|
||||
pub dark: String,
|
||||
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<DeTable>, input: &str) -> Result<Self, toml::de::Error> {
|
||||
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<String> {
|
||||
Ok(match if light { self.light.as_str() } else { self.dark.as_str() } {
|
||||
"" => String::new(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<String, Service>);
|
||||
|
||||
impl Deref for Services {
|
||||
|
|
@ -30,12 +32,13 @@ impl Services {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
pub(super) fn deserialize_over<'de, D>(mut self, deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let map: HashMap<String, Service> = HashMap::deserialize(deserializer)?;
|
||||
self.0.extend(map);
|
||||
pub(super) fn deserialize_over_with<'de>(
|
||||
mut self,
|
||||
table: Spanned<DeTable<'de>>,
|
||||
) -> Result<Self, toml::de::Error> {
|
||||
for (key, value) in table.into_inner() {
|
||||
self.0.insert(key.into_inner().into_owned(), <_>::deserialize(value.into_deserializer())?);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Vfs> = OnceCell::const_new();
|
||||
|
||||
async fn init() -> io::Result<Vfs> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -79,22 +79,16 @@ 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
|
||||
local only = #outs == 1 and outs[1]
|
||||
if only and not only.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)
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue